基于cookie的django-rest-jwt认证

       关于JWT(Json Web Token)是一种较新的用户认证方式,官网在这里,网上有篇中文解释写的很好,点此跳转

用户认证(Authentication)和用户授权(Authorization)是两个不同的概念,认证解决的是“有没有”的问题,而授权解决的是“能不能”的问题。

一般用到JWT认证的情况大多都是配合REST框架使用,比如我大Django的Django-REST-framework框架,就已经有了现成的三方库django-rest-framework-jwt。不过这个库默认只支持基于Header传递信息,所以改成基于Cookie方式还需要我们来手动处理一下。

关于安装,直接使用pip安装即可,在settings.py中,先来修改django-restframework的基本配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'app.myauth.CookieAuthentication',
),
'PAGE_SIZE': 20,
}
 
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds= 300), # 过期时间
'JWT_AUTH_HEADER_PREFIX': 'ABC', # 请求头前缀
}

 

至于JWT_AUTH更多的配置见官网,比如使用什么加密算法等等。

接下来编写认证代码,这里逻辑很简单,app/myauth.py内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# coding=utf-8
from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
 
 
class CookieAuthentication(BaseJSONWebTokenAuthentication):
"""自定义JWT认证,从cookie中获取认证信息"""
 
def get_jwt_value(self, request):
# print request.COOKIES
return request.COOKIES.get(api_settings.JWT_AUTH_HEADER_PREFIX.upper(), '')
 
def authenticate_header(self, request):
"""
注意这里:
返回一个字符串作为`WWW-Authenticate`的值,http响应头中有`WWW-Authenticate`才会返回401.
否则返回403.
"""
return '{0} realm="{1}"'.format(api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm)

 

然后编写我们自己的获取JWT-Token的View,编辑app/views.py添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from datetime import datetime
from rest_framework import status
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.views import ObtainJSONWebToken
 
class CookieJSONWebToken(ObtainJSONWebToken):
"""
接受post请求生成JWT-Token并设置cookie
"""
 
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
 
if serializer.is_valid():
user = serializer.object.get( 'user') or request.user
token = serializer.object.get( 'token')
response_data = api_settings.JWT_RESPONSE_PAYLOAD_HANDLER(
token, user, request)
res = Response(response_data)
res.set_cookie(api_settings.JWT_AUTH_HEADER_PREFIX.upper(), value=response_data[
'token'], httponly= True, expires=datetime.now() + api_settings.JWT_EXPIRATION_DELTA)
return res
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

扫描二维码关注公众号,回复: 374002 查看本文章

这里注意设定httponly属性,防止造成安全漏洞。

最后,修改urls.py添加我们的view:

1
2
3
4
5
6
7
8
 
from app import views as baseview
 
urlpatterns = [
...
url( r'^api-token-auth/', baseview.CookieJSONWebToken.as_view()),
...
]

 

设定完成后,就可以通过向api-token-auth这个URL提交用户名密码来获取JWT-Token了,并且在浏览器使用ajax请求获取那些登录后的数据会自动带上cookie进行认证。至于如何解决跨域,可以使用django-cors-headers

这里多说一句,使用这种方式登录时候你会发现在头部即便有Set-Cookie,但ajax请求成功获取token后并不会自动设置cookie,因为浏览器把这个头忽略了。这里就涉及到另一个HTTP头Access-Control-Allow-Credentials,只有这个设置成true时候浏览器才会处理ajax返回来的Cookie信息。相关概念可以参考这里

这里我用axios为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
axios({
method: 'post',
url: 'http://127.0.0.1:9090/api-token-auth/',
data: {
username: 'username',
password: 'password'
},
withCredentials: true,
})
.then( function(response) {
console.log(response);
})
.catch( function(error) {
console.log(error);
});

 

这里注意,设置withCredentialstrue,此时发送请求,你会在浏览器控制台中看到已拦截跨源请求:同源策略禁止读取位于 http://127.0.0.1:9090/api-token-auth/ 的远程资源。(原因:CORS 头中的 'Access-Control-Allow-Credentials' 预期为 'true')

解决这个问题,首先安装上面的django-cors-headers,然后修改settings.py添加下面的配置即可:

1
2
3
4
5
6
# CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
'localhost:8080',
)
# 准许ajax设置cookie
CORS_ALLOW_CREDENTIALS = True

 

这里需要注意的,如果CORS_ALLOW_CREDENTIALS设置为True,那么Access-Control-Allow-Origin则不能为通配符*了,所以使用报名单的方式。

猜你喜欢

转载自hugoren.iteye.com/blog/2379743