最新消息:20210917 已从crifan.com换到crifan.org

【已解决】Django中对于单个REST的接口把JWT的token验证放到query string的url中

Django crifan 2285浏览 0评论

折腾:

【已解决】Ant Design的Reactjs页面中点击下载word文件不弹框而直接下载

期间,前端希望实现:

用户点击一个按钮下载文件,但是不要现在的弹框。

而能想到的其中一种可能的方案是:

对于文件下载的url来说,可以考虑把token放到url的query string中

这样就可以直接把完整url放到a的href了,点击后就可以直接下载到文件了,估计就不用弹框了。

所以需要改造:

对于目前已有web前端是:

export async function scriptWordExport(scriptID) {

  return request(`${apiPrefix}/scripts/${scriptID}/script_word_export/`, {

    headers: constructHeaders(),

  });

}

即把JWT的header是放在headers参数中的

需要先去想办法,看看server后端能否支持把header放在url中而不是headers中,毕竟后端是Django的框架,有些模式是固定的,包括权限验证,要去看看这个单独的url,能否改造

django jwt token inside url

django jwt token in url not header

python – Generate Django JWT token from inside a view – Stack Overflow

或许可以重写jwt的request的handler去实现我们要的效果?

django-restframework-jwt asks for username/password when using JWT token – Stack Overflow

python – How to get logged in use and protect urls in jwt token auth django REST framework – Stack Overflow

python – How can i make django-rest-framework-jwt return token on registration? – Stack Overflow

JWT authentication doesn’t work for custom controller in Django – Stack Overflow

Full stack Django: Quick start with JWT auth and React/Redux (Part I)

不过此处突然发现,下载文件对应后台的接口script_word_export,之前别人写的代码是:

    @detail_route(

        methods=[‘get’],

        url_path=’script_word_export’,

        url_name=’script_word_export’,

        permission_classes=[

            AllowAny,

        ])

    def script_word_export(self, request, pk=None):

        logger.info("request=%s", request)

而不是别的需要授权的接口的:

    @list_route(

        methods=[‘get’],

        url_path=’group_owner_script_list’,

        url_name=’group_owner_script_list’,

        pagination_class=StandardResultsSetPagination,

        permission_classes=[

            IsAuthenticated, IsUserScriptFunctionGroup,

        ])

    def group_owner_script_list(self, request):

其中IsAuthenticated和IsUserScriptFunctionGroup看起来就是需要相关的用户权限的。

-》估计script_word_export就不需要登录用户就可以啊。

去web端试试

果然可以:

http://localhost:65000/api/v1/scripts/25c2b9ec-431c-4ea5-acb4-d207aa6a7622/script_word_export/

那么就好好办了,先去web端改:

            <a href="javascript:;" onClick={() => this.handleExport(record)} >导出</a>

为a的href试试:

src/routes/Script/ScriptList.js

export function scriptWordExportUrl(scriptID) {

  return `${apiPrefix}/scripts/${scriptID}/script_word_export/`;

}

和:

src/services/api.js

import { scriptWordExportUrl } from ‘../../services/api’;

      {

        。。。

        render: (text, record) => {

          // http://localhost:65000/api/v1/scripts/25c2b9ec-431c-4ea5-acb4-d207aa6a7622/script_word_export/

          let exportScritpUrl = scriptWordExportUrl(record.id)

          return (

            <Fragment>

。。。

              <a href={exportScritpUrl} >导出</a>

              <Divider type="vertical" />

              <Popconfirm title="是否删除次剧本?" onConfirm={() => this.handleDeleteScript(record.id)} >

                <a>删除</a>

              </Popconfirm>

            </Fragment>

          )

        },

      },

然后试了试,果然是可以的:

虽然此处基本上可以工作了,但是为了更加安全:

不让别人没有登录就可以随便导出下载脚本内容为docx的word文件

所以还是加上JWT的token才可以

Modern Django: Part 4: Adding authentication to React SPA using DRF — v1k45

Is JWT in Authorization header a standard? · Issue #31 · GetBlimp/django-rest-framework-jwt

python – Token in query string with Django REST Framework’s TokenAuthentication – Stack Overflow

django rest framework – Add Token to headers request in TEST mode DRF – Stack Overflow

How to pass JWT token through header in react native to django rest api? – Stack Overflow

然后给后端加上权限验证后:

    @detail_route(

        methods=[‘get’],

        url_path=’script_word_export’,

        url_name=’script_word_export’,

        permission_classes=[

            # AllowAny,

            IsAuthenticated, IsUserScriptFunctionGroup

        ])

    def script_word_export(self, request, pk=None):

再去前端浏览器,确保没有登录

(残留的之前的cookie和storage后

其中:

Chrome的调试中application-》Application-〉Storage-》Clear this site data

后,才能清空全部数据:

的前提下,直接打开:

http://localhost:65000/api/v1/scripts/f8692c21-68f8-4673-a896-0cc4202c27f7/script_word_export/

就无法下载文件了:

https://stackoverflow.com/questions/29433416/token-in-query-string-with-django-rest-frameworks-tokenauthentication

是我们要的

但是即使实现了,如何去配置呢

参考:

http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

http://www.django-rest-framework.org/api-guide/authentication/#setting-the-authentication-scheme

是去配置:

-》

DEFAULT_AUTHENTICATION_CLASSES

但是此处已经采用了:

JSON Web Token Authentication

了,不是默认的TokenAuthentication

所以就又不太清楚,如何配置,如何复写了。

项目中搜:TokenAuthentication

REST_FRAMEWORK = {

    ‘DEFAULT_PERMISSION_CLASSES’: (

        ‘rest_framework.permissions.IsAuthenticated’,

    ),

    ‘DEFAULT_AUTHENTICATION_CLASSES’: (

        ‘rest_framework_jwt.authentication.JSONWebTokenAuthentication’,

        ‘rest_framework.authentication.SessionAuthentication’,

        ‘rest_framework.authentication.BasicAuthentication’,

    ),

好像是:

先去搞懂:JSONWebTokenAuthentication

的逻辑,然后override这个JSONWebTokenAuthentication,支持query string获取token

比如叫做:JWTAuthByQueryStringOrHeader

然后放到项目中合适的位置后,

比如在:apps/util/jwt.py

再去设置这里的:

DEFAULT_AUTHENTICATION_CLASSES为app.util.jwt.JWTAuthSupportQueryString

所以先去搞清楚:

JSONWebTokenAuthentication

django JSONWebTokenAuthentication in query string

django override JSONWebTokenAuthentication

已经给出基本例子了

但是需要弄的更清楚

Django REST framework JWT

How does login work with Django? · Issue #264 · graphql-python/graphene-django

python – Override the authToken views in Django Rest – Stack Overflow

JSON WEB TOKEN BASED Authentication Backend for Django Project

然后去写代码

结果用:

class TokenAuthSupportQueryString(BaseJSONWebTokenAuthentication):

结果找不到BaseJSONWebTokenAuthentication

去搜:BaseJSONWebTokenAuthentication

django-rest-framework-jwt/authentication.py at master · GetBlimp/django-rest-framework-jwt

BaseJSONWebTokenAuthentication throws error · Issue #242 · GetBlimp/django-rest-framework-jwt

基于cookie的django-rest-jwt认证 – Hi!Roy!

加上:

from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication

好像就可以了。

继续写代码

结果有了文件:

apps/util/JWTAuthByQueryStringOrHeader.py

后,却提示找不到:

  File "/usr/local/lib/python3.6/site-packages/rest_framework/settings.py", line 187, in import_from_string

    raise ImportError(msg)

ImportError: Could not import ‘apps.util.JWTAuthByQueryStringOrHeader’ for API setting ‘DEFAULT_AUTHENTICATION_CLASSES’. AttributeError: module ‘apps.util’ has no attribute ‘JWTAuthByQueryStringOrHeader’.

配置从:

REST_FRAMEWORK = {

    ‘DEFAULT_PERMISSION_CLASSES’: (

        ‘rest_framework.permissions.IsAuthenticated’,

    ),

    ‘DEFAULT_AUTHENTICATION_CLASSES’: (

        # ‘rest_framework_jwt.authentication.JSONWebTokenAuthentication’,

        ‘apps.util.JWTAuthByQueryStringOrHeader’,

        ‘rest_framework.authentication.SessionAuthentication’,

        ‘rest_framework.authentication.BasicAuthentication’,

    ),

改为:

apps.util.JWTAuthByQueryStringOrHeader.JWTAuthByQueryStringOrHeader

结果才可以。

结果代码:

from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication

import logging

logger = logging.getLogger(‘django’)

# class JWTAuthByQueryStringOrHeader(JSONWebTokenAuthentication):

class JWTAuthByQueryStringOrHeader(BaseJSONWebTokenAuthentication):

    """

    Extend the TokenAuthentication class to support querystring authentication

    in the form of "http://www.example.com/?jwt_token=<token_key>"

    """

    def authenticate(self, request):

        # Check if ‘token_auth’ is in the request query params.

        # Give precedence to ‘Authorization’ header.

        logger.info("request=%s", request)

        logger.info("request.QUERY_PARAMS=%s", request.QUERY_PARAMS)

        logger.info("request.META=%s", request.META)

        if (‘jwt_token’ in request.QUERY_PARAMS) and (‘HTTP_AUTHORIZATION’ not in request.META):

            jwtTokenFromQueryString = request.QUERY_PARAMS.get(‘jwt_token’)

            logger.info("jwtTokenFromQueryString=%s", jwtTokenFromQueryString)

            return self.authenticate_credentials(jwtTokenFromQueryString)

        else:

            return super(JWTAuthByQueryStringOrHeader, self).authenticate(request)

出错了:

  File "/usr/local/lib/python3.6/site-packages/rest_framework/request.py", line 222, in user

    self._authenticate()

  File "/usr/local/lib/python3.6/site-packages/rest_framework/request.py", line 375, in _authenticate

    user_auth_tuple = authenticator.authenticate(self)

  File "/Users/crifan/dev/dev_root/company/xxx/apps/util/JWTAuthByQueryStringOrHeader.py", line 17, in authenticate

    logger.info("request.QUERY_PARAMS=%s", request.QUERY_PARAMS)

  File "/usr/local/lib/python3.6/site-packages/rest_framework/request.py", line 443, in QUERY_PARAMS

    ‘`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` ‘

NotImplementedError: `request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` since version 3.0, and has been fully removed as of version 3.2.

且看到最新的:

https://github.com/GetBlimp/django-rest-framework-jwt/blob/master/rest_framework_jwt/authentication.py

的:BaseJSONWebTokenAuthentication中,对于jwt的token的获取也是用的单独的函数:

        jwt_value = self.get_jwt_value(request)

        if jwt_value is None:

            return None

或许应该继承JSONWebTokenAuthentication?

或许继承了BaseJSONWebTokenAuthentication,只需要重写self.get_jwt_value?

NotImplementedError: `request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` since version 3.0, and has been fully removed as of version 3.2.

djangorestframework no longer implements request.QUERY_PARAMS · Issue #6 · jpadilla/django-rest-framework-jsonp

3.2 Announcement – Django REST framework

django rest framework – hymn from the weekend

然后去改为:

from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication

import logging

logger = logging.getLogger(‘django’)

# class JWTAuthByQueryStringOrHeader(JSONWebTokenAuthentication):

class JWTAuthByQueryStringOrHeader(BaseJSONWebTokenAuthentication):

    """

    Extend the TokenAuthentication class to support querystring authentication

    in the form of "http://www.example.com/?jwt_token=<token_key>"

    """

    def authenticate(self, request):

        # Check if ‘token_auth’ is in the request query params.

        # Give precedence to ‘Authorization’ header.

        # queryParams = request.QUERY_PARAMS

        logger.info("request=%s", request)

        queryParams = request.query_params

        reqMeta = request.META

        logger.info("queryParams=%s", queryParams)

        logger.info("reqMeta=%s", reqMeta)

        if (‘jwt_token’ in queryParams) and (‘HTTP_AUTHORIZATION’ not in reqMeta):

            jwtTokenFromQueryString = queryParams.get(‘jwt_token’)

            logger.info("jwtTokenFromQueryString=%s", jwtTokenFromQueryString)

            return self.authenticate_credentials(jwtTokenFromQueryString)

        else:

            return super(JWTAuthByQueryStringOrHeader, self).authenticate(request)

另外报错:

  File "/usr/local/lib/python3.6/site-packages/rest_framework/request.py", line 375, in _authenticate

    user_auth_tuple = authenticator.authenticate(self)

  File "/Users/crifan/dev/dev_root/xxx/apps/util/JWTAuthByQueryStringOrHeader.py", line 28, in authenticate

    return super(JWTAuthByQueryStringOrHeader, self).authenticate(request)

  File "/usr/local/lib/python3.6/site-packages/rest_framework_jwt/authentication.py", line 28, in authenticate

    jwt_value = self.get_jwt_value(request)

rest_framework.request.WrappedAttributeError: ‘JWTAuthByQueryStringOrHeader’ object has no attribute ‘get_jwt_value’

很明显是:JWTAuthByQueryStringOrHeader继承了BaseJSONWebTokenAuthentication,但是却没有实现get_jwt_value

rest_framework.request.WrappedAttributeError object has no attribute ‘get_jwt_value’

参考:

http://www.hi-roy.com/2017/01/11/基于cookie的django-rest-jwt认证/

去实现:get_jwt_value

最终实现了要的效果。

【总结】

最终是通过继承JSONWebTokenAuthentication的get_jwt_value,优先从query string=query parameter中获取jwt的token,如果没有再去header的Authorization中获取jwt的token。

具体写法是:

apps/util/jwt_token.py

# from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication

from rest_framework_jwt.authentication import JSONWebTokenAuthentication

from rest_framework.authentication import get_authorization_header

import logging

logger = logging.getLogger(‘django’)

class JWTAuthByQueryStringOrHeader(JSONWebTokenAuthentication):

# class JWTAuthByQueryStringOrHeader(BaseJSONWebTokenAuthentication):

    """

    Extend the TokenAuthentication class to support querystring authentication

    in the form of "http://www.example.com/?jwt_token=<token_key>"

    """

    def get_jwt_value(self, request):

        # Check if ‘jwt_token’ is in the request query params.

        # Give precedence to ‘Authorization’ header.

        logger.debug("request=%s", request)

        queryParams = request.query_params

        reqMeta = request.META

        logger.debug("queryParams=%s", queryParams)

        logger.debug("reqMeta=%s", reqMeta)

        if (‘jwt_token’ in queryParams) and (‘HTTP_AUTHORIZATION’ not in reqMeta):

            jwt_token = queryParams.get(‘jwt_token’)

            # got jwt token from query parameter

            logger.debug("jwt_token=%s", jwt_token)

            return jwt_token

        else:

            # call JSONWebTokenAuthentication’s get_jwt_value

            # to get jwt token from header of ‘Authorization’

            return super(JWTAuthByQueryStringOrHeader, self).get_jwt_value(request)

然后再去更新配置,把之前的JSONWebTokenAuthentication改为JWTAuthByQueryStringOrHeader:

/conf/development/settings.py

# django-restframework and jwt settings

REST_FRAMEWORK = {

    …

    ‘DEFAULT_AUTHENTICATION_CLASSES’: (

        # ‘rest_framework_jwt.authentication.JSONWebTokenAuthentication’,

        ‘apps.util.jwt_token.JWTAuthByQueryStringOrHeader’,

        ‘rest_framework.authentication.SessionAuthentication’,

        ‘rest_framework.authentication.BasicAuthentication’,

    ),

    …

}

然后web前端,之前的接口还是按照:

在header中传入token:

Authorization: JWT your_token

然后也支持在url中通过jwt_token传递token:

http://localhost:65000/api/v1/scripts/3d9e77b0-e538-49b8-8790-60301ca79e1d/script_word_export/?jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWVkMGEwZDgtMmFiYi00MDFkLTk5NTYtMTQ5MzcxNDIwMGUzIiwidXNlcm5hbWUiOiJsc2R2aW5jZW50IiwiZXhwIjoxNTMxOTAyOTU0LCJlbWFpbCI6InZpbmNlbnQuY2hlbkBuYXR1cmxpbmcuY29tIn0.wheM7Fmv8y8ysz0pp-yUHFqfk-IQ5a8n_8OplbYkj7s

转载请注明:在路上 » 【已解决】Django中对于单个REST的接口把JWT的token验证放到query string的url中

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
92 queries in 0.199 seconds, using 23.55MB memory