JWT

 

什么是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的原理及实现方法

  

    https://jwt.io/#debugger-io

 

 

 

用户提交用户名和密码给服务端如果登录成功,使用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 = []

 

posted @ 2020-07-10 23:44  不忘初心❤  阅读(168)  评论(0编辑  收藏  举报