drf之jwt


一、cookie、session、token简介

# 无cookie时期
很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器, 
不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新的HTTP协议,
就是请求加响应, 尤其是我不用记住是谁刚刚发了HTTP请求, 每个请求对我来说都是全新的。这段时间很嗨皮
# cookie+session认证时期 但是随着交互式Web应用的兴起,像在线购物网站,需要登录的网站等等,马上就面临一个问题,那就是要管理会话,必须记住哪些人登录系统, 哪些人往自己的购物车中放商品,
也就是说我必须把每个人区分开,这就是一个不小的挑战,因为HTTP请求是无状态的,所以想出的办法就是给大家发一个会话标识(session id), 说白了就是一个随机的字串,每个人收到的都不一样,
每次大家向我发起HTTP请求的时候,把这个字符串给一并捎过来, 这样我就能区分开谁是谁了
# cookie是保存到客户端上以K:V键值对形式保存的 # session是保存到服务端上的 K是随机字符串V是用户信息,然后通过sessionid获取, sessionid保存到cookie中
# 服务端保存sessionid会有压力 所以出现了token验证
token: 三段式 第一段:头:公司信息,加密方式。。。 {} 第二段:荷载:真正的数据 {name:lqz,id:1} 第三段:签名,通过第一段和第二段,通过某种加密方式加密得到的 aafasdfas 加密的时候肯定是要加盐处理的 token的使用分两个阶段 -登录成功后的【签发token阶段】---》生成三段 -登录成功访问某个接口的 【验证阶段】---》验证token是否合法 # token是三段式,服务端生成的,存放在客户端(浏览器就放在cookie中,移动端:存在移动端中) 使用token的认证机制,服务端还要存数据吗? token是服务端的生成,客户端保存,服务端的不存储token

更多知识点:https://www.cnblogs.com/liuqingzheng/p/8990027.html

 

二、jwt的原理介绍

# jwt(Jason Web Token), Token应用于web方向称之为jwt

# 构成:有头、荷载、签名构成
    -其实就是一段字符串,有三部分信息构成,然后将这个三部分用.链接到一起就构成了jwt字符串,eg:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

# heard:头
    -声明类型:jwt
    -声明加密的算法, 通常直接使用 HMAC SHA256 ,也可以不用声明
    -公司信息
    {'typ':'jwt', 'alg':'HS256'...}
然后通过base64的编码就会变成一段字符串
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

# payload:荷载
        -exp: jwt的过期时间,这个过期时间必须要大于签发时间
        -iat: jwt的签发时间
       - 用户信息: 用户信息
       {'exp':'123456', 'name':'lqz', 'id',2} 
然后通过base64的编码就会变成一段字符串
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

# signature:签名
    把头和荷载编码之后 在将它们两个加密后得到:
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

# 注意:secret是保存在服务器端的(加密方式+盐),jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了

# jwt最核心的就是
    -签发:登入接口签发
    -认证:认证类认证

1.session的认证机制

 

2.jwt的认证机制

jwt认证机制是最快的 因为jwt是直接在代码中直接签发和认证不用去数据库中读取

 

 

 三、base64的编码与解码

# 我们在签发和验证token的时候 都需要把密文给编码和解码这样就会相对安全的 所以现在我们可以来学学base的编码和解码

# base64简介
    base64可以把字符串编码成base64的编码格式(字母,数字,=)
eg:
    eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9

# base64可以把base64编码的字符串,解码回原来的格式

# 应用场景:
    - jwt中使用
    - 网络中传输字符串就可以使用base64编码
    - 网络中传输图片,也可能使用base64的编码

1.代码演示

1.1编码

import base64
import json

d = {'name': 'jason', 'password': 123}
res = json.dumps(d)
print(res)

info = base64.b64encode(res.encode('utf8'))
print(info)  # eyJuYW1lIjogImphc29uIiwgInBhc3N3b3JkIjogMTIzfQ==
# 这样就是把json格式的字符串编码成了base的编码格式

1.2 解码

info_decode = base64.b64decode(info)
print(info_decode)  # b'{"name": "jason", "password": 123}'

d1 = json.loads(info_decode)  # 再通过json转换成python的字典即可
print(d1)  # {'name': 'jason', 'password': 123}

四、drf-jwt的快速使用

# jwt 签发(登入接口)和认证(认证)

# django中使用drf需要下载
    -https://github.com/jpadilla/django-rest-framework-jwt  (比较老)
    -https://github.com/jazzband/djangorestframework-simplejwt (比较新)
   -也可以自己写

# 安装
    pip3 install djangorestframework-jwt

# 安装号之后我们就可以使用了

# 快速使用
    # 1.因为这个模块是配合django自带的user表一起使用的 所以要先迁移表
    # 2.创建用户
   # 3.因为只有登入用户才能签发token,所以我们要编写一个登入接口
        但是这个模块已经帮我们写好了所以不需要写
    # 4.我们只需要在路径上配置好即可

1.代码演示

1.1 签发

# url.py
from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    path('login/', obtain_jwt_token),
]
# 这样token的签发就写好了

# 我们在前端访问的时候只需要访问 /login/接口的时候就会返回token

 

 1.2 认证

# 当token签发号之后 我们就可以带着这个token然后访问某个接口即可 然后校验这个token如果通过才能让这个用户访问

# url:
from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    path('user/', views.UserView.as_view()),
]

# view
from rest_framework.views import APIView
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

class UserView(APIView):
    authentication_classes = [JSONWebTokenAuthentication, ]
    permission_classes = [IsAuthenticated, ]
    def get(self, request):
        return Response('ok')

 

 

五、修改jwt返回格式

# jwt返回格式只返回了一个token这样不符合我们的返回格式 所以我们需要重写返回格式

# 其实读了源码我们可以知道jwt返回token也是在jwt的配置文件中配置了

# 所以我们只需要自己写一个函数 然后在配置文件中配置即可

1.代码演示

# url
from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    path('login/', obtain_jwt_token),
]

# 函数
def jwt(token, user=None, request=None):
    return {
        'code': 100,
        'msg': '登入成功',
        'username': user.username,
        'id': user.id,
        'token': token,
    }


# 配置文件
JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.jwt.jwt',
}

 

 六、自定义user表,签发token

# 向上面的第三方模块是需要跟自带的use表配合使用 
# 但是我们也可以自己自定义user表签发token

# 首先我们要创建一个用户表,并创建一个用户
class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)

# 然后写一个登录接口
from .models import User
from rest_framework_jwt.settings import api_settings
from rest_framework.response import Response
from rest_framework.exceptions import APIException

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

class LoginView(APIView):
    def post(self, request):
        try:
            username = request.data.get('username')
            password = request.data.get('password')
            user = User.objects.get(username=username, password=password)
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)  # token和payload是从源码中看来的
            return Response({
                'code': 100,
                'msg': ' 登入成功',
                'username': username,
                'id': user.id,
                'token': token
            })
        except Exception as e:  # 这里可以有很多错误 不过我统一变成了错误,其实还可以捕获 用户没写什么的
            raise APIException('用户名获密码错误')

# 路由
urlpatterns = [
    path('login/', views.LoginView.as_view())
]

 

 1.jwt源码分析

# 我们可以知道jwt帮我写了token的签发和认证 那么我们其实可以看一下它是怎么写的


# 首先入口肯定是路由匹配
from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    path('login/', obtain_jwt_token),
]
# 点进去看的时候发现是一个函数的内存地址 
obtain_jwt_token = ObtainJSONWebToken.as_view()

# 那么我们就看一下ObtainJSONWebToken这个怎么写的
class ObtainJSONWebToken(JSONWebTokenAPIView):
    serializer_class = JSONWebTokenSerializer

# ObtainJSONWebToken就是写了一个序列化类 然后继承了JSONWebTokenAPIView
    def post(self, request, *args, **kwargs)  # 就是写了一个post方法
        serializer = self.get_serializer(data=request.data)      
        if serializer.is_valid():    # 验证用户名和密码是否正确
            user = serializer.object.get('user') or request.user
            token = serializer.object.get('token')  # 正确 就产生一个token  然后token是从序列化类中产生的 所以我们可以看一下这个序列化类
            response_data = jwt_response_payload_handler(token, user, request)
            response = Response(response_data)
            if api_settings.JWT_AUTH_COOKIE:
                expiration = (datetime.utcnow() +
                              api_settings.JWT_EXPIRATION_DELTA)
                response.set_cookie(api_settings.JWT_AUTH_COOKIE,  # 然后将token在和用户信息保存到cookie中
                                    token,
                                    expires=expiration,
                                    httponly=True) 
            return response  # 返回出去
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# 序列化类就是这个JSONWebTokenSerializer
    def validate(self, attrs):
            if user:
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)

                payload = jwt_payload_handler(user)  # 这个就是根据用户产生荷载

                return {
                    'token': jwt_encode_handler(payload),  # 然后根据荷载产生token 
                    'user': user
                }
# 然后这两个参数就是上面已经搭配好了
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

# 在jwt的配置文件中是可以找到的
DEFAULTS = {
    'JWT_ENCODE_HANDLER':
    'rest_framework_jwt.utils.jwt_encode_handler',
'JWT_PAYLOAD_HANDLER':
    'rest_framework_jwt.utils.jwt_payload_handler',
}

# 所以就是这里让我们自己写的时候借助了jwt的产生token

作业

1.自定义认证类,验证token

from rest_framework_jwt.settings import api_settings
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from .models import User
import jwt
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER

class TokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        jwt_value = request.META.get('HTTP_TOKEN')  # 这个token是我们自己签发的token
        if jwt_value is None:
            return None
        try:
            payload = jwt_decode_handler(jwt_value)
            print(payload)
        except jwt.ExpiredSignature:
            msg = ('token过期了 ')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = ('token解析错误')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed('未知错误')

        user = User.objects.filter(pk=payload['user_id']).first()

        return user, jwt_value

view.py

class UsersView(APIView):
    authentication_classes = [TokenAuthentication, ]

    def get(self, request):
        return Response('ok')

 

posted @ 2022-10-12 20:58  stephen_hao  阅读(177)  评论(0编辑  收藏  举报