JWT 实现
1. 基于传统的JWT认证
2. jwt
1. 基于传统的token认证
用户将用户名和密码发送给服务端,服务端返回其token值(uuid),并将其token值保存入库,以后用户再登录时需要携带token值进行验证。
例:login
class Login(APIView): def post(self, request, *args, **kwargs): ''' 获取用户名密码 与数据库是否一致 一致,生成uuid()值,保存数据库,返回给前端 ''' username = request.data.get('name') passwd = request.data.get('passwd') user_object = models.User_info.objects.filter(username=username, password=passwd).first() if not user_object: return Response({'code': 1001, 'error': '用户名或密码输入有误'}) random_string = str(uuid.uuid4()) user_object.token = random_string user_object.save() return Response({'code': 200, 'data': random_string})
例:order
class Order(APIView): def get(self, request): ''' query_params # 获取url中的token值 ''' token = request.query_params.get('token') if not token: return Response({'code': 2002, 'error': '登录成功后才能访问'}) user_obj = models.User_info.objects.filter(token=token).first() if not user_obj: return Response({'code': 2001, 'error': 'token不正确'}) return Response({'code': 200, 'data': '订单列表'})
2. jwt
用户登录,服务端返回token值(服务端不保存),以后用户再来访问需要携带token,服务端获取token,再做token检验。
优势: 相较于传统的jwt服务端不需要进行保存。
3. jwt 实现过程
用户提交用户名和密码给服务端,如果登录成功,使用jwt创建一个token,并给用户返回。
eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9. # 第一部分
eyJpZCI6MSwibmFtZSI6ImFkbWluIiwiZXhwIjoxNjA1NTI3NjkxfQ. # 第二部分
n9f7gShAedaVVhq2dkA8ClpJ5TI1AhCogalSqOLCjuU # 第三部分
注意:jwt 生产的token是由三段字符串组成,并用 . 连接起来。
第一段字符串,HEADER, 内部包含算法 (默认哈希256)/和token类型。
json转化成字符串,然后做base64url加密(base64加密;+ _)
# 构造header headers = { 'typ': 'jwt', 'alg': 'HS256' }
第二段字符串,payload,自定义值
json转化成字符串,然后做base64url加密(base64加密;+ _)
# 构造payload payload = { 'user_id': user_obj.id, # 自定义用户ID 'username': user_obj.username, # 自定义用户名 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=1) # 超时时间 }
第三段字符串
第一步:第1,2部分密文拼接起来 eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6ImFkbWluIiwiZXhwIjoxNjA1NTI3NjkxfQ 第二步:对前2部分密文进行HS256加密 + 加盐 第三部:对HS256加密后的密文再做base65url 加密
以后用户再来访问的时候,需要携带token,后端需要对token进行校验,获取token
第一步:对token进行切割
eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9. # 第一部分 eyJpZCI6MSwibmFtZSI6ImFkbWluIiwiZXhwIjoxNjA1NTI3NjkxfQ. # 第二部分 n9f7gShAedaVVhq2dkA8ClpJ5TI1AhCogalSqOLCjuU # 第三部分
第二步:对第二部分进行base64url解密,并获取payload信息,检测token是否超时?
payload = { 'user_id': user_obj.id, # 自定义用户ID 'username': user_obj.username, # 自定义用户名 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=1) # 超时时间 }
第三步:把第1,2端拼接,再次执行sha256加密。
第一步:第1,2部分密文拼接起来 eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6ImFkbWluIiwiZXhwIjoxNjA1NTI3NjkxfQ 第二步:对前2部分密文进行HS256加密 + 加盐 密文 = base64解密(n9f7gShAedaVVhq2dkA8ClpJ5TI1AhCogalSqOLCjuU)
例: 代码演示
pip install pyjwt
from rest_framework.response import Response from rest_framework.views import APIView class Jwt_login(APIView): def post(self, request, *args, **kwargs): username = request.data.get('username') password = request.data.get('password') SALT = 'qpo0rerngnejg3n5j3nk2' if not all([username, password]): return Response({'code': 10010, 'error': '输入完整的用户名密码'}) user_obj = models.User_info.objects.filter(username=username, password=password).first() if not user_obj: return Response({'code': 10020, 'error': '用户名密码不正确'}) # 构造header headers = { 'typ': 'jwt', 'alg': 'HS256' } # 构造payload payload = { 'user_id': user_obj.id, # 自定义用户ID 'username': user_obj.username, # 自定义用户名 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=1) # 超时时间 } result = jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers).decode('utf-8') return Response({'code': 200, 'data': result})
from jwt import exceptions class Jwt_order(APIView): def get(self, request, *args, **kwargs): token = request.query_params.get('token') SALT = 'qpo0rerngnejg3n5j3nk2' msg = None if not token: return Response({'code': 3001, 'error': 'token值不能为空'}) try: payload = jwt.decode(token, SALT, True) return Response({'code':200,'data':payload}) except exceptions.ExpiredSignatureError: msg = 'token已失效' except jwt.DecodeError: msg = 'token认证失败' except jwt.InvalidTokenError: msg = '非法的token' return Response({'code':20020,'data':msg})
例: 代码演示2升级
# 在子应用中新建文件夹,新建py,将返回token值的代码写入 from app01.utils.jwt_auth import create_token class Pro_login(APIView): authentication_classes = [] def post(self, request, *args, **kwargs): username = request.data.get('username') password = request.data.get('password') if not all([username, password]): return Response({'code': 10010, 'error': '输入完整的用户名密码'}) user_obj = models.User_info.objects.filter(username=username, password=password).first() if not user_obj: return Response({'code': 10020, 'error': '用户名密码不正确'}) token = create_token({'id':user_obj.id,'name':user_obj.username},1) return Response({'code': 200, 'data': token})
# 子应用返回token代码如下 from django.conf import settings import jwt import datetime from rest_framework.response import Response def create_token(payload,timeout): SALT = settings.SECRET_KEY # 构造header headers = { 'typ': 'jwt', 'alg': 'HS256' } # 构造payload payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=timeout) # 超时时间 result = jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers).decode('utf-8') return result
# 在子应用新建文件夹,新建py,将token认证的代码写入 from app01.extensions.auth import JwtQueryParamsAuthentication class Pro_order(APIView): authentication_classes = [JwtQueryParamsAuthentication, ] # 类似于Django中间件,先执行这个 def get(self, request,*args, **kwargs): print(request.user) print(request.auth) return Response('订单列表')
from django.conf import settings from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from rest_framework.response import Response import jwt from jwt import exceptions ''' 获取token并判断token的合法性 1. 切割 2. 解密第二段/判断过期 3. 验证第三段的合法性 ''' class JwtQueryParamsAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get('token') SALT = settings.SECRET_KEY if not token: raise AuthenticationFailed({'code':'token不存在'}) try: payload = jwt.decode(token, SALT, True) except exceptions.ExpiredSignatureError: raise AuthenticationFailed({'code':1,'error':"token已失效"}) except jwt.DecodeError: raise AuthenticationFailed({'code':2,'error':"token认证失败"}) except jwt.InvalidTokenError: raise AuthenticationFailed({'code':3,'error':"非法的token"}) return (payload, token) # 三种操作 # 1. 抛出异常,后续不执行 # 2. return一个元组 (1,2),认证通过,在视图中如果调用request.user就是调用第一个值,request.auth就是元祖的第二个值 # 3. None 表示下一次认证
例:代码演示3 全局
from app01.utils.jwt_auth import create_token class Pro_login(APIView): authentication_classes = [] # j加入后表示不受全局限制 def post(self, request, *args, **kwargs): username = request.data.get('username') password = request.data.get('password') if not all([username, password]): return Response({'code': 10010, 'error': '输入完整的用户名密码'}) user_obj = models.User_info.objects.filter(username=username, password=password).first() if not user_obj: return Response({'code': 10020, 'error': '用户名密码不正确'}) token = create_token({'id':user_obj.id,'name':user_obj.username},1) return Response({'code': 200, 'data': token}) from app01.extensions.auth import JwtQueryParamsAuthentication class Pro_order(APIView): # authentication_classes = [JwtQueryParamsAuthentication, ] # 类似于Django中间件,先执行这个 def get(self, request,*args, **kwargs): print(request.user) print(request.auth) return Response('订单列表')
用户登录返回token,与认证token不变。
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES" : ["app01.extensions.auth.JwtQueryParamsAuthentication", ] } # 在APIView 中底层有DEFAULT_AUTHENTICATION_CLASSES方法。
图片路径如下: