在 Django REST framework 使用 JWT
0. 参考博客
1. JWT or Cookie&Session
1.1 JWT
- JWT(
JSON WEB Token
)是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。讲人话?是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。 - JWT通常由三部分组成: 头信息(header), 负载(payload)和签名(signature)
-
header
- 通常包含 token类型 和 采用的加密算法 两部分
{ "alg":"HS256", "typ":"JWT" }
- 通常包含 token类型 和 采用的加密算法 两部分
-
payload
- 负载,它包含了claim, Claim是一些实体(通常指的用户)的状态和额外的元数据,有三种类型的claim:reserved, public 和 private.Reserved claims: 这些claim是JWT预先定义的,在JWT中并不会强制使用它们,而是推荐使用,常用的有 iss(签发者),exp(过期时间戳), sub(面向的用户), aud(接收方), iat(签发时间)。 Public claims:根据需要定义自己的字段,注意应该避免冲突 Private claims:这些是自定义的字段,可以用来在双方之间交换信息 负载使用的例子:
{ "sub": "1234567890", "name": "John Doe", "admin": true}
上述的负载需要经过Base64Url编码后作为JWT结构的第二部分。
- 负载,它包含了claim, Claim是一些实体(通常指的用户)的状态和额外的元数据,有三种类型的claim:reserved, public 和 private.Reserved claims: 这些claim是JWT预先定义的,在JWT中并不会强制使用它们,而是推荐使用,常用的有 iss(签发者),exp(过期时间戳), sub(面向的用户), aud(接收方), iat(签发时间)。 Public claims:根据需要定义自己的字段,注意应该避免冲突 Private claims:这些是自定义的字段,可以用来在双方之间交换信息 负载使用的例子:
-
signature
- 前面两部分信息加密,然后加上我们自己设定的一个secret的字符串,进行再次加密,这次加密是header中声明的加密算法(HS256),构造成一个签名信息
-
- 最后变成
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMywidXNlcxxxxxxxxxxxXNoZXJfdGVzdCIsImxxxxA1NDc0OCwiZW1haWwiOiIxMjNAMTIzMS5jb20ifQ.DF2a63Up9wqVvEwQrb7r3z3aOdkDcFCgNNL9pjVN3R8
1.2 Cookie&Session
- 具体可以参考 cookie 和session 的区别详解
1.3 两者优劣
- 网上 文章一大推,个人经历场景有点少,没办法详细比较,留坑~
- 现在的感觉?两者都不是 “放之四海而皆准”的,总有场景有坑。。。
- 那为什么要用 token? !!! 又不是我决定的(如果我决定?团队熟悉哪个就用哪个!),出现什么就解决什么问题呗!
2. REST framework JWT Auth
2.1 说明
- 使用
REST framework JWT Auth
, 官方文档 - 环境要求
- 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)
- 刚发现,项目环境一个也不符合,但目前没发现什么坑!(不要问为什么还用了,我是接盘的。。。私以为,应该文档未更新)
- djangorestframework 3.8.2
- Django 1.11.1
- Python 3.6.6
- 项目配置
3. 正常操作
3.1 前端怎么传递?
- 官方文档里面有写,在 HTTP hard
Authorization
的JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMywidXNlcm5hbWUiOiJwdXNoZXJfdGVzdCIsImV4cCI6MTUzOTUyODUxMywiZW1haWwiOiIxMjNAMTIzMS5jb20ifQ.nm9odcWAXL05YM8TP0XTZhjbhuiqq6bYeeRjbcorgKw
。ps: 格式为:JWT
+空格
+token
。默认为JWT
,配置中可更改
3.0 对整个 ViewSet 设置权限
from rest_framework.authentication import SessionAuthentication
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class EducationViewSet(mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin,
viewsets.GenericViewSet):
queryset = Education.objects.all()
serializer_class = EducationSerializer
permission_classes = (IsAuthenticated, IsOwnerOrReadOnlyDepth)
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
- 说明,未带token 的会直接被框架拦截
3.1 view 里操作?
self.request.user
即 当前登录用户- 想起来再补充。。。
3.2自定义 负载里面的 信息?
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
# obj 为一个对象
payload = jwt_payload_handler(obj)
# dict 即 自定义 信息 ,格式为 字典
token = jwt_encode_handler(dict)
4. “骚操作”
4.1 部分有权限接口,但前端无法自定义请求?
- 根据同源,不修改前端代码
- 启用 cookie
-
在配置中增加
JWT_AUTH = { "JWT_AUTH_COOKIE": "token", # 键值名字随意订 }
-
登录接口中返回增加 cookie, 键名与上一步相同,将token 传过去
-
确保 ViewSet 中
authentication_classes
有SessionAuthentication
,没有则增加
-
- 启用 cookie
- 修改前端代码,将token 从HTTP 参数传入
- 自定义认证,从请求参数去取
from rest_framework_jwt.authentication import JSONWebTokenAuthentication class GetAuthentication(JSONWebTokenAuthentication): def get_jwt_value(self, request): # 告诉框架 token 在哪里 return request.query_params.get("jwt") # http 参数为 jwt
- 引入
GetAuthentication
, ViewSet 中authentication_classes
增加GetAuthentication
- 自定义认证,从请求参数去取