折腾:
【已解决】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 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/
就无法下载文件了:
是我们要的
但是即使实现了,如何去配置呢
参考:
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
但是此处已经采用了:
了,不是默认的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
已经给出基本例子了
但是需要弄的更清楚
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.
且看到最新的:
的: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.
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:
转载请注明:在路上 » 【已解决】Django中对于单个REST的接口把JWT的token验证放到query string的url中