1.简介

Josn Web Token:常用于前后端分离中/微信小程序/app开发中的用户认证

官网地址:https://jwt.io/

2. 传统的用户认证流程

 

 

 基于传统的token进行用户验证流程

  1. 用户登录,携带用户登录信息
  2. 后端校验用户数据,并生成对应的token
  3. 后端将token返回给前端,并保存在后端(数据库或者缓存中)
  4. 用户再次访问,携带token
  5. 后端检查用户携带的token是否和保存的一致
  6. 返回响应给前端

 案例

视图

from rest_framework.views import APIView
from rest_framework.response import Response
from api import models
import uuid


# Create your views here.


class LoginAPIView(APIView):
    def post(self, request, *args, **kwargs):
        # 检验用户信息。验证成功,返回token
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = models.UserModel.objects.filter(username=username, password=password).first()
        if not user_obj:
            return Response({'code': 1000, 'error': '用户名或者密码错误'})
        token = str(uuid.uuid4())
        user_obj.token = token
        user_obj.save()
        return Response({'code': 2000, 'token': token})


class OrderAPIView(APIView):
    def get(self, request, *args, **kwargs):
        # 获取订单信息,但是用户必须是已登录的
        token = request.query_params.get('token')
        if not token:
            return Response({'coder': 1000, 'error': '用户需要登录才能访问'})
        user_obj = models.UserModel.objects.filter(token=token).first()
        if not user_obj:
            return Response({'coder': 1000, 'error': 'token不正确'})
        return Response({'coder': 2000, 'data': '订单列表'})

模型类

from django.db import models


# Create your models here.

class UserModel(models.Model):
    username = models.CharField(verbose_name='用户名', max_length=32, null=False)
    password = models.CharField(verbose_name='密码', max_length=32, null=False)
    token = models.CharField(verbose_name='TOKEN', max_length=128, null=True)

    class Meta:
        db_table = 'user_tb'
        verbose_name = '用户表'
        verbose_name_plural = '用户表'

路由

from django.urls import path
from api import views

urlpatterns = [
    path('login/', views.LoginAPIView.as_view()),
    path('order/', views.OrderAPIView.as_view()),
]

 

3. JWT

备注:base64加密是可以反向解密的,HS256和md5则是不可逆的

jwt格式解析

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

字符串由两个点分割成三段

  • 第一段是JWT的header,包含着加密算法和type,常常为固定值
    将header转成字符串之后进行base64加密
    {
      "alg": "HS256",
      "typ": "JWT"
    }
  • 第二段为payload,是真实数据,常为不敏感信息
    将数据转化为字符串之后进行base64加密
    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
  • 第三段为verify,将前两段的密文拼接然后HS256加密+加盐,最后将密文再次进行base64加密
    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      
    ) secret base64 encoded

     

校验过程解析

  • 后端获取jwt token
  • 通过点进行分割成三段
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  • 对第一段进行base64解密,获取加密方式
    {
      "alg": "HS256",
      "typ": "JWT"
    }
  • 对第二段进行base64解密,获取payload数据
    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
  • 将第三段进行base64解密,得到HS256的密文A
  • 将前两段的密文进行拼接然后进行HS256加密 + 加盐 得到密文B
  • 将密文A B进行比较,判断token是否正确

 

JWT加密的核心就是加盐,盐必须保证只有服务端知道

 

 

 

 基于JWT的token进行用户验证流程

  1. 用户登录,携带用户登录信息
  2. 后端校验用户数据,并基于jwt生成对应的token
  3. 后端将token返回给前端
  4. 用户再次访问,携带token
  5. 后端检查用户携带的token是否和保存的一致
  6. 返回响应给前端

两种验证最大的区别就是JWT验证不再需要存储token数值,在多用户的情况下,这可以很大程度上减轻服务压力

4. 应用

了解了JWT的原理之后,我们可以手动实现JWT的加密解密过程,,不过通过第三方库可以更加便捷

官网地址:https://jwt.io/libraries?language=Python

pip install pyjwt

 

加密:

可以指定加密的算法,加密数据和加密的盐

解密:指定token值和解密算法,解密的盐,返回的是payload数据

import jwt
encoded_jwt = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
print(encoded_jwt)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
jwt.decode(encoded_jwt, "secret", algorithms=["HS256"])
# {'some': 'payload'}

 

示例JWT的加密:

import jwt
import datetime

# 构造salt
salt = "asdkljal2o384u290slkafjl@U&#^(Q@#dsjfha"

# 构造header
header = {
    "alg": "HS256",
    "typ": "JWT"
}

# 构造payload
data = {
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022,
    'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5)  # 超时时间
}

print(jwt.encode(payload=data, key=salt, headers=header))  #
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE2Njk0NTExMjR9.I3dSGGKMNKilgLBtPDpvxjHapNYOVoLFEnfgHkqEkww

 

示例JWT解密

print(jwt.decode(
    jwt='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE2Njk0NTExMjR9.I3dSGGKMNKilgLBtPDpvxjHapNYOVoLFEnfgHkqEkww',
    key=salt,
    algorithms='HS256'))

# {'sub': '1234567890', 'name': 'John Doe', 'iat': 1516239022, 'exp': 1669451124}

 

drf中开发实战

在用户登录成功之后,生成token并返回,用户再次来访问时需携带token。基于上述案例进行改进开发

视图函数

from rest_framework.views import APIView
from rest_framework.response import Response
from api import models
import uuid
from api.authention.jwtAuthentication import JwtAuthentication
from api.utils import jwt_create
class ProLoginAPIView(APIView):
    def post(self, request, *args, **kwargs):
        # 检验用户信息。验证成功,返回token
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = models.UserModel.objects.filter(username=username, password=password).first()
        if not user_obj:
            return Response({'code': 1000, 'error': '用户名或者密码错误'})
        token = jwt_create.jwt_create({'username': username})
        return Response({'code': 2000, 'token': token})


class ProOrderAPIView(APIView):
    authentication_classes = [JwtAuthentication, ]

    def get(self, request, *args, **kwargs):
        print(request.user)
        return Response({'coder': 2000, 'data': '订单列表'})

自定义验证类JwtAuthentication

# encoding:utf-8
# author:kunmzhao
# email:1102669474@qq.com

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from django.conf import settings
import jwt


class JwtAuthentication(BaseAuthentication):
    def authenticate(self, request):
        salt = settings.SECRET_KEY
        token = request.query_params.get('token')
        if not token:
            raise AuthenticationFailed('没有token')
        try:
            data = jwt.decode(jwt=token, key=salt, algorithms='HS256')
        except jwt.exceptions.ExpiredSignatureError:
            raise AuthenticationFailed('token已过期')
        except jwt.exceptions.InvalidTokenError:
            raise AuthenticationFailed('无效的token')
        except Exception as e:
            raise AuthenticationFailed(str(e))
        return data, token

自定义jwt_create

# encoding:utf-8
# author:kunmzhao
# email:1102669474@qq.com
import jwt
import datetime
from django.conf import settings


def jwt_create(data, exp=None):
    """

    :param data: 加密数据
    :param exp: 过期时间,单位为分钟
    :return: token
    """
    # 构造header
    header = {
        "alg": "HS256",
        "typ": "JWT"
    }

    key = settings.SECRET_KEY
    if exp:
        data['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=exp)
    return jwt.encode(payload=data, key=key, headers=header)

 

posted on 2022-11-26 17:04  阿明明  阅读(448)  评论(0编辑  收藏  举报