• python-JWT(Json Web Token)-pyjwt


    JWT的引入

    传统登录认证流程: 

    1. 用户第一次登录时, 生成一个token并返回给前台, 同时将其与用户主键一同存在后台服务器上(数据库或缓存中)
    2. 下一次访问需要登录的页面时, 将token一起传入
    3. 后台拿着token去数据库或缓存中查找是否存在该token, 存在则认证通过, 否则认证不通过

    传统认证的缺点:

    1. token存在后台, 增加了存储和读取的开销
    2. 当存在多个后台服务器时, 需同步共享token, 比较麻烦

    JWT认证流程(解决了传统认证的问题):

    1. 用户第一次登录时, 生成一个token, 但后台不存储该token

    2. 下一次访问需要登录的页面时, 将token一起传入

    3. 后台拿着token进行解析和校验, 若解析成功则认证通过, 否则认证不通过

    JWT加密原理:

    生成的token分为三个部分: HEADER.PAYLOAD.SIGNATURE, 这三个部分都是可逆算法base64加密后的字符串, 最后用点号(.)拼接.如:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    1. HEADER

    代表了加密算法和token类型, 若不显示指定, 默认为:
    {
      "alg": "HS256",
      "typ": "JWT"
    }
    加密后结果为: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

    2. PAYLOAD

    代表了想要传输的业务信息和token的过期时间(可选), 例如:
    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022,
      "exp": 451154141
    }
    加密后结果为: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

    3. SIGNATURE

    JWT的关键, 其规则是将前面两段加密后的密文再加上自定义的盐值一起拼接后, 再通过不可逆算法HS256(具体使用的是HEADER中的算法)进行加密, 最后再对该密文进行可逆算法base64加密

    盐值(salt): 指的是加密时加入的自定义的字符串, 最好是随机或者杂乱的字符串, 这样更能够确定加密后字符串的唯一性, django中可以使用settings中的SECRET_KEY

    加密后结果为: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    JWT解密原理:

    拿到请求中传过来的token后:
    1. 按.号拆分token, 拿到三段值
    2. 对这三段密文进行base64解密, 从明文中拿到加密算法和业务数据以及过期时间
    3. 再次拼接前两段的密文和自定义的盐值(该盐值必须和创建token时的盐值一样), 使用HEADER中的算法进行加密
    4. 将加密的结果和拿到的token中的第三段解密的明文进行比较, 若完全一致则说明认证成功, 若不一致则说明token被篡改过, 认证失败

    对JWT三段式的思考:

    JWT的根本思想就是将业务数据通过不可逆算法加密存储在token中, 那么为什么要搞成三段式这么复杂呢?
    直接把业务数据加上盐值, 然后用默认不可逆算法生成一段密文字符串进行传输不就可以了吗?
    这样加密时是比较简单, 但是解密时却由于不可逆算法而拿不到其中的业务数据, 所以确实需要再加一段式来单独存储业务数据
    JWT又加了一段用来存储加密算法, 能够让使用者自己确定具体使用什么算法进行加密, 增加了可扩展性

    python中使用JWT

    pyjwt

    这是python使用JWT的基础包, 在jwt官网中python语言点赞最多的就是pyjwt, 安装方式为:  pip install pyjwt , 这个包已经把加密和解密的逻辑写好了, 我们只需要传入加密算法/业务数据/盐值即可

    在rest_framework中使用pyjwt

    定义两个接口, 登录(login)和查看订单(order), 只有登录过的用户才能成功访问查看订单接口, 我们可以在登录接口中若成功登录则返回jwt的加密token, 在订单接口中自定义一个认证类, 在认证类中校验token

    1. 编辑urls.py

    from django.urls import path
    from users import views
    
    urlpatterns = [
        path('login/', views.LoginView.as_view()),
        path('order/', views.OrderView.as_view()),
    ]

    2. 编辑登录和订单视图类

    1. 登录成功后, 调用获取token的方法 create_token() , 传入参数为用户信息和token过期时间(单位: 分钟), 默认1分钟

    2. 在订单视图类中设置认证类 JWTAuthentication

    3. create_token和JWTAuthentication都定义在utils包的JWTAuth.py中

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from utils.JWTAuth import create_token, JWTAuthentication

    class
    LoginView(APIView): def post(self, request, *args, **kwargs): # 获取用户名密码 name = request.data.get('name') pwd = request.data.get('pwd') # 获取User对象 try: user = models.User.objects.filter(name=name, pwd=pwd).first() except Exception: return Response({'status': 1, 'errmsg': '用户名或密码不正确!'}) # 获取token token = create_token({'id': user.id, 'name': user.name}, 1) # 返回成功响应 return Response({'status': 0, 'token': token}) class OrderView(APIView): authentication_classes = [JWTAuthentication, ] def get(self, request): return Response({'status': 0, 'msg': 'ok'})

    3. 编辑JWTAuth.py

    import jwt
    from jwt import exceptions as JWTException
    from django.conf import settings
    import datetime
    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    
    def create_token(payload, timeout=1):
        # 给传过来的业务数据增加一个过期时间限制
        payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=timeout)
        # 定义盐值
        salt = settings.SECRET_KEY
        # 默认不可逆加密算法为HS256
        token = jwt.encode(payload=payload, key=salt)
        return token
    
    class JWTAuthentication(BaseAuthentication):
        def authenticate(self, request):
            # 从url参数中获取token
            token = request.query_params.get('token')
            # 盐值
            salt = settings.SECRET_KEY
            # 解码token
            try:
                payload = jwt.decode(jwt=token, key=salt, verify=True)
            except JWTException.ExpiredSignature:
                raise AuthenticationFailed('token已失效')
            except jwt.DecodeError:
                raise AuthenticationFailed('token认证失败')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('非法的token')
    
            return payload.get('name'), token

    注意: 设置过期时间时, 一定是在payload段中设置, 且键名固定为'exp', 值为  datetime.datetime.utcnow() + datetime.timedelta(xxxx) 

  • 相关阅读:
    JS中的prototype与面向对象
    机电传动控制第二周学习笔记
    第三周作业
    机电传动控制第四周作业
    第五周学习笔记
    机电传动控制第一周学习笔记
    个人项目图书管理系统登陆功能模拟
    GITHUB使用及入门总结
    我的github地址
    工作压力改变了我?
  • 原文地址:https://www.cnblogs.com/gcxblogs/p/13376058.html
Copyright © 2020-2023  润新知