什么是JWT?
json web token,一般用于用户认证(前后端分离/微信小程序/app开发)
部分公司用的pip install djangorestframework-jwt本质就是调用pyjwt
传统的token验证工作流程:
用户登录,后台效验用户名和密码正确会生成一个Token(随机的字符串),会在数据库保存一份,然后将生成的token返回给前端,下次用户访问其他页面会带着token,
根据这个token去数据库里对比判断用户是否登录。
实现代码:
# 创建一个Django项目
我们用restframework来写,注意在settings里的app里要把rest_framework加上
# 创建一个model表,要有token字段
# 写urls
# 在views里写
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView from rest_framework.response import Response from app01 import models import uuid # Create your views here. class LoginView(APIView): def post(self,request,*args,**kwargs): name = request.data.get('name') pwd = request.data.get('password') userobj = models.UserInfo.objects.filter(name=name,password=pwd).first() if not userobj: return Response({'code': 300, 'msg': '用户名密码错误'}) token = str(uuid.uuid4()) userobj.token = token userobj.save() return Response({'code':100,'msg':'登录成功'}) class OrderView(APIView): def get(self,request,*args,**kwargs): token = request.query_params.get('token') print(token,request.query_params) if not token: return Response({'code':301,'msg':'请登录后访问'}) user_obj = models.UserInfo.objects.filter(token=token).first() if not user_obj: return Response({'code':302,'msg':'token失效,请重新登录'}) return Response('订单页面')
# 用postman测试
-----------------------------------------------------------------------------------------------------------------------------
登录成功后会在数据库里产生token
不带token访问会要求登录后访问
乱带一个会校验token
带数据库里的token才可以查看信息
这是传统的token验证的做法
下面来介绍一下JWT的原理及实现方法
用户提交用户名和密码给服务端如果登录成功,使用jwt创建一个token,并给用户返回
eyJhbGciOiJIUzIINiIsInR5cCI6IkpxvCJ9. eyJzdwIioiIxMjMONTY3ODkwIiwibmFtZSI6IkpvaG4gRG91Iiwi awFOIjoxNTE2MjM5MDIyfQ. Sf1KxwRJSMeKKF 2QT4fwpMeJf36POk6yJV_ adQssw5c
注意: jwt 生成的 token 是由三段字符串组成并且用 "." 连接起来。
Token的加密过程
第一段字符串,HEADER,内部包含算法/token类型
json转化成字符串,然后做base64url加密(base64加密;里面的“+”,“/”等换成“_”)
{ "a1g": "HS256", "typ": "JWT" }
第二段字符串,payload,自定义值
json转化成字符串,然后做base64url加密(base64加密;里面的“+”,“/”等换成“_”)
{ "id"; "123123", "name": "chenggen" , "exp": 1516239022 #超时时间 }
第三段字符串:
第一步:第1,2部分密文拼接起来 eyJhbGcioiJIUzIINiIsInR5cCI6IkpXVC19.eyJzdwIioiIxMjMONTY30DkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaNFOIjoxNTE2MjM5MDIyfQ 第二步:对前2部分密文进行HS256加密+加盐 第三步:对HS256加密后的密文再做base64ur1加密
后端的效验过程
以后用户再来访问时候需要携带token,后端霖要对token进行校验。
获取token
第一步:对token进行切割
eyJhbGcioiJIUzIINiIsInRScCI6IkpXVCJ9. eyJzdwIiOiIxMjMONTY30DkwIiwibmFtZSI6IkpvaG4gRG91Iiwi alwFOIjoxNTE2MjM5MDIyfQ. Sf]KxwRJSMeKKF 2QT4fwpMeJf36POk6yJV_ adQssw5c
第二步:对第二段进行base64url解密,并获取payload信息检测token是否已经超时?
{
"id": "123123", "name": ”chenggen", "exp": 1516239022 #超时时间
}
第三步:把第1,2端拼接再次执行sha256加密”
第一步:第1,2部分密文拼接起来 eyJhbGcioiJIUzINiIsInRScCI6IkpXVC39.eyJzdwTi0iTxMjMONTY30DkwIiwibmFtZSI6IkpvaG4gRG9lIiwiainFOIjoxNTE2MjM5MDIyfQ 第二步:对前2部分密文进行HS256加密+加盐 密文= base64解密(sf1KxwRJSMeKKF2QT4fwpMeJf36POk6yJV adQssw5c)如果相等,表示token未被修改过. (认证通过)
Jwt的应用
安装:pip install pyjwt
class JwtLoginView(APIView): def post(self,request,*args,**kwargs): name = request.data.get('name') pwd = request.data.get('password') userobj = models.UserInfo.objects.filter(name=name,password=pwd).first() if not userobj: return Response({'code': 300, 'msg': '用户名密码错误'}) import jwt import datetime salt = 'deghrtyjtdhtegjdyujhtjhrgtrjttf' # 构造header , 这里不写默认的也是 headers = { 'typ': 'jwt', 'alg': 'HS256' } # 构造payload payload = { 'user_id': userobj.id, # 自定义用户ID 'username': userobj.name, # 自定义用户名 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # 超时时间,取现在时间,五分钟后token失效 } token = jwt.encode(payload=payload, key=salt, algorithm="HS256", headers=headers).decode('utf-8') return Response({'code':100,'msg':'登录成功','token':token})
postman 测试
JWTorder页面
class JwtOrderView(APIView): def get(self,request,*args,**kwargs): import jwt from jwt import exceptions salt = 'deghrtyjtdhtegjdyujhtjhrgtrjttf' token = request.query_params.get('token') payload = None msg = None try: payload = jwt.decode(token,salt,True) except exceptions.ExpiredSignatureError: msg = 'token已失效' except jwt.DecodeError: msg = 'token认证失败' except jwt.InvalidTokenError: msg = '非法的token' if not payload: return Response({'code':303,'error':msg}) print(payload['user_id'],payload['username']) return Response('订单页面')
在项目中这么写会很乱,所以我们可以子自定义写文件导入
urls.py
url(r'^pro/login/$', views.ProLoginView.as_view()), url(r'^pro/order/$', views.ProOrderView.as_view()),
自定义文件
auth.py
from django.conf import settings from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed import jwt from jwt import exceptions class JwtQueryParamsAuthentication(BaseAuthentication): def authenticate(self, request): # 获取token并判断token的合法性 token = request.query_params.get('token') salt = settings.SECRET_KEY try: payload = jwt.decode(token,salt,True) except exceptions.ExpiredSignatureError: raise AuthenticationFailed({'code':1003,'error':'token已失效'}) except jwt.DecodeError: raise AuthenticationFailed({'code':1003,'error':'token认证失败'}) except jwt.InvalidTokenError: raise AuthenticationFailed({'code':1003,'error':'非法的token'}) # 三种操作 #1.抛出异常,后续不在执行 #2.return一个元组(1,2),认证通过,在视图中如果调用request.user就是元组的第一个值,requset.auth就是第二个值 #3.None return (payload,token)
jwt_auth.py
import jwt import datetime from django.conf import settings def create_token(payload,timeout=5): salt = settings.SECRET_KEY # 构造header header = { 'typ':'jwt', 'alg':'HS256' } # 构造payload payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=timeout) token = jwt.encode(payload=payload,key=salt,algorithm="HS256",headers=header).decode('utf-8') return token
views.py
from app01.extensions.auth import JwtQueryParamsAuthentication from app01.utils.jwt_auth import create_token class ProLoginView(APIView): def post(self,request,*args,**kwargs): name = request.data.get('name') pwd = request.data.get('password') user_obj = models.UserInfo.objects.filter(name=name,password=pwd).first() if not user_obj: return Response({'code':301,'error':'用户名或密码错误'}) token = create_token({'id':user_obj.id,'name':user_obj.name}) return Response({'code':302,'data':token}) class ProOrderView(APIView): authentication_classes = [JwtQueryParamsAuthentication,] def get(self,request,*args,**kwargs): print(request.user) return Response('订单列表')
最后在settings里设置一个全局的认证
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES':['app01.extensions.auth.JwtQueryParamsAuthentication'] }
这样写的话全局都会有token认证,但是登陆页面我们不能验证,否则会死循环了,只需要加
authention_classes = []