在DRF中科学的使用JWT
JWT简介:
JWT全称Json Web Token,JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快 **自包含(Self-contained)**:负载中包含了所有用户所需要的信息,避免了多次查询数据库。
JWT的相较于Cookie的优势:
传统的web认证方式多采用session和cookie,这种方式需要在服务端维护一个session,随着用户量增大,开销也会越来越大。在移动客户端中维护cookie比较麻烦。而Token模式只需要在服务端生成Token,客户端保存这个Token,然后每次请求将这个Token传输至服务端做认证解析即可。
JWT的构成:
JWT由三部分构成:Header+Payload+Signature
Header
头部用于描述该JWT的基本信息,如:
1
2
3
4{
"alg": "HS256",
"typ": "JWT"
}Payload
有效载荷用于携带一些需要的自定义字段,一般情况下有如下内容:
1
2
3
4
5
6{
"sub": "1234567890", //面向的用户
"name": "John Doe", //用户名
"iat": 1516239022, //签发时间
"exp": 1535598369 //token失效时间
}这里你可以把你想要的任何内容放进来。
Signature
签名部分是将上述两部分base64编码,再加上一个用户自定义的密钥,然后用“.”连接起来使用Header中描述的算法进行加密运算再base64编码而成。
1
2
3
4
5HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)最后生成的Token为上述三部分用“.”号拼接起来的字符串。
如何在DjangoRestframework中使用JWT?
上面了解了JWT的构成,如何实际应用在DRF中呢?下面看操作
环境要求
- Python (2.7, 3.3, 3.4, 3.5)
- Django (1.8, 1.9, 1.10)
- Django REST Framework (3.0, 3.1, 3.2, 3.3, 3.4, 3.5)
安装jwt库
1 | pip install djangorestframework-jwt |
在settings.py中添加JWT认证所需的AuthencitationClass
1 | REST_FRAMEWORK = { |
在urls.py中添加获取token的URL
1 | from rest_framework_jwt.views import obtain_jwt_token |
OK,现在一个Token认证的Demo就已经完成了,我们用Postman测试一下。
可以看出已经成功的获取到了Token,然后我们分别将token的三部分进行base64解码:
1 | { |
哇,payload里边多了一些我们不想要的字段,比如邮箱,user_id?多登陆几次,嗯,每次生成的Token都不一致,那把之前生成的Token放进Http头里边请求一下需要登录验证的接口看一下,居然还可以使用!!这显得不够科学嘛,如果历史Token泄露了,又没到过期时间咋办?这显然不太科学嘛!!!改,必须改。
科学的使用djangorestframework_jwt
修改payload的生成
首先找到库文件自己的payloadhandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36def jwt_payload_handler(user):
username_field = get_username_field()
username = get_username(user)
warnings.warn(
'The following fields will be removed in the future: '
'`email` and `user_id`. ',
DeprecationWarning
)
payload = {
'user_id': user.pk,
'username': username,
'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA
}
if hasattr(user, 'email'):
payload['email'] = user.email
if isinstance(user.pk, uuid.UUID):
payload['user_id'] = str(user.pk)
payload[username_field] = username
# Include original issued at time for a brand new token,
# to allow token refresh
if api_settings.JWT_ALLOW_REFRESH:
payload['orig_iat'] = timegm(
datetime.utcnow().utctimetuple()
)
if api_settings.JWT_AUDIENCE is not None:
payload['aud'] = api_settings.JWT_AUDIENCE
if api_settings.JWT_ISSUER is not None:
payload['iss'] = api_settings.JWT_ISSUER
return payload那么我们自己重新写一个payloadhandler吧,把不需要的字段删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40import jwt
import uuid
import warnings
from django.contrib.auth import get_user_model
from calendar import timegm
from datetime import datetime
from rest_framework_jwt.compat import get_username
from rest_framework_jwt.compat import get_username_field
from rest_framework_jwt.settings import api_settings
def jwt_payload_handler(user):
username_field = get_username_field()
username = get_username(user)
warnings.warn(
'The following fields will be removed in the future: '
'`email` and `user_id`. ',
DeprecationWarning
)
payload = {
'username': username,
'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA
}
# Include original issued at time for a brand new token,
# to allow token refresh
if api_settings.JWT_ALLOW_REFRESH:
payload['orig_iat'] = timegm(
datetime.utcnow().utctimetuple()
)
if api_settings.JWT_AUDIENCE is not None:
payload['aud'] = api_settings.JWT_AUDIENCE
if api_settings.JWT_ISSUER is not None:
payload['iss'] = api_settings.JWT_ISSUER
return payload
JWT的唯一性验证
payload中额外的字段解决了,那么Token的唯一性也要解决一下吧,不然所有登录生成的Token只要没有过期都可以正常通过验证使用,有点不太安全。先看看它库文件自身的验证逻辑:
可以看出在authencitation方法中没有对token进行唯一性验证,把登录和Token验证也修改一把:
1 | from rest_framework_jwt.serializers import JSONWebTokenSerializer |
修改Token验证的类,加入唯一性验证:
1 | from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
修改了这么多地方,settings.py中,Auth Class和JWT_AUTH的配置也需要同步的修改一下:
1 | REST_FRAMEWORK = { |
至于Token的失效与刷新可以通过logout后再login实现:
1 |
|
以上就是我对于djangorestframework_jwt的一点小小的实践经验,欢迎各位大佬指出本菜鸡的不足,共同探讨。