DRF JWT认证(二)

DRF JWT认证(二)

DRF JWT认证(二)

上篇中对JWT有了基本的认知,这篇来略谈JWT的使用

签发:一般我们登录成功后签发一个token串,token串分为三段,头部,载荷,签名

1)用基本信息公司信息存储json字典,采用base64算法得到 头字符串
2)用关键信息存储json字典,采用base64算法得到 荷载字符串,过期时间,用户id,用户名
3)用头、体加密字符串通过加密算法+秘钥加密得到 签名字符串
拼接成token返回给前台

认证:根据客户端带token的请求 反解出 user 对象

1)将token按 . 拆分为三段字符串,第一段 头部加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间是安全信息,确保token没过期
3)再用 第一段 + 第二段 + 加密方式和秘钥得到一个加密串,与第三段 签名字符串 进行比较,通过后才能代表第二段校验得到的user对象就是合法的登录用户

JWT可以使用如下两种:

djangorestframework-jwtdjangorestframework-simplejwt

djangorestframework-jwthttps://github.com/jpadilla/django-rest-framework-jwt

djangorestframework-simplejwthttps://github.com/jazzband/djangorestframework-simplejwt

区别https://blog.csdn.net/lady_killer9/article/details/103075076

官网文档https://jpadilla.github.io/django-rest-framework-jwt/

django中快速使用JWT

导入pip3 install djangorestframework-jwt

如何签发?

步骤

  1. 路由中配置
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('login/', obtain_jwt_token),
]
  1. 使用接口测试工具发送post请求到后端,就能基于auth的user表签发token
{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ.P1Y8Z3WhdndHoWE0PjW-ygd53Ng0T46U04oY8_0StwI"
}

DRF JWT认证(二)

base64反解

import base64

第一段
s1 = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
print(base64.b64decode(s1))
b'{"typ":"JWT","alg":"HS256"}'

第二段
s2 = b'eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ=='
print(base64.b64decode(s2))
b'{"user_id":1,"username":"Hammer","exp":1649524662,"email":""}'
我们发现,第二段可以解密用户的信息,存在一定的风险,可以使用,但不能更改,就像您的身份证丢失了一样,其他人可以在网吧上网而不挂失。<details><summary>*<font color='gray'>[En]</font>*</summary>*<font color='gray'>We found that the second paragraph can decrypt the user's information, there is a certain risk, it can be used, but can not be changed, just like your ID card has been lost, others can go online in Internet cafes without reporting the loss.</font>*</details>

'''第三段不能不能反解,只能做base64解码,第三段使用base64编码只是为了统一格式'''

如何认证?

我们没有认证的时候,直接访问接口就可以返回数据,比如访问 /books/发送GET请求就可以获取所有book信息,那么现在添加认证,需要访问通过才能访问才更合理

步骤

  • 视图中配置,必须配置 认证类权限类
  • 访问需要在请求头中使用,携带签发的token串,格式是:
key是Authorization
value是jwt token串
Authorization : jwt token串
'''注意jwt和token串中间有空格'''

视图

from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class BookView(GenericViewSet,ListModelMixin):
    ···
     # JSONWebTokenAuthentication :rest_framework_jwt模块写的认证类
    authentication_classes = [JSONWebTokenAuthentication,]
    # 需要配合一个权限类
    permission_classes = [IsAuthenticated,]
    ···

DRF JWT认证(二)

定制签发token返回格式

JWT默认的配置是,我们登录成功后只返回一个token串,这也是默认的配置,我们如果想签发token后返回更多数据需要我们自定制

步骤

  1. 写一个函数,返回什么格式,前端就能看见什么格式
  2. 在配置文件中配置 JWT_AUTH

utils.py

定义签发token(登陆接口)返回格式
def jwt_response_payload_handler(token, user=None, request=None):
    return {
        'code': 100,
        'msg': "登陆成功",
        'token': token,
        'username': user.username
    }

settings.py

JWT_AUTH = {
      'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
  }

DRF JWT认证(二)

JWT源码分析

签发源码分析

1.入口:path('login/', obtain_jwt_token)

2.obtain_jwt_token--->obtain_jwt_token = ObtainJSONWebToken.as_view()
ObtainJSONWebToken.as_view(),其实就是一个视图类.as_view()

3.ObtainJSONWebToken类源码
'''
class ObtainJSONWebToken(JSONWebTokenAPIView):
    serializer_class = JSONWebTokenSerializer
'''

4.登录签发token肯定需要一个post方法出来,但是ObtainJSONWebToken类内没有父类JSONWebTokenAPIView写了post方法:
    def post(self, request, *args, **kwargs):
        # 获取数据:{'username': 'Hammer', 'password': '7410'}
        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
            response_data = jwt_response_payload_handler(token, user, request)
           #  {'code': 100, 'msg': '登陆成功', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTU4MTU0NiwiZW1haWwiOiIifQ.2oAjKQ90SV2S9Yxrwppo7BwAOv0xFW4i4AHHBX5Cg2Q', 'username': 'Hammer'}
            response = Response(response_data)
            if api_settings.JWT_AUTH_COOKIE:
               ···
            return response # 定制什么返回什么

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

5.get_serializer(data=request.data)如何获取到用户数据?
JSONWebTokenSerializer序列化类中全局钩子中获取当前登录用户和签发token
···
payload = jwt_payload_handler(user)
                return {
                    'token': jwt_encode_handler(payload),
                    'user': user
                }
···

签发总结

从obtain_jwt_token开始, 通过ObtainJSONWebToken视图类处理,其实是父类JSONWebTokenAPIView的post方法通过传入的用户名和密码处理获取当前用户,签发了token

认证源码分析

包含要使用的权限类的视图类中的身份验证类<details><summary>*<font color='gray'>[En]</font>*</summary>*<font color='gray'>Authentication class in view class with permission class to use</font>*</details>
    authentication_classes = [JSONWebTokenAuthentication, ]
    permission_classes = [IsAuthenticated, ]

我们在前面写过,如果需要认证肯定需要重写authenticate方法,这里从列表内的认证类作为入口分析:

'''认证类源码'''
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
    www_authenticate_realm = 'api'

    def get_jwt_value(self, request):
        # 获取传入的Authorization:jwt token串,然后切分
        auth = get_authorization_header(request).split()
        auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
        # 获取不到的情况
        if not auth:
            if api_settings.JWT_AUTH_COOKIE:
                return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
            return None  # 直接返回None,也不会报错,所以必须搭配权限类使用

        ···

        return auth[1]  # 一切符合判断条件,通过split切分的列表索引到token串
'''认证类父类源码'''
def authenticate(self, request):
        jwt_value = self.get_jwt_value(request) # 获取真正的token,三段式,上面分析
        if jwt_value is None: # 如果没传token,就不认证了,直接通过,所以需要配合权限类一起用
            return None

        try:
            payload = jwt_decode_handler(jwt_value)# 验证签名
        except jwt.ExpiredSignature:
            msg = _('Signature has expired.') # 过期了
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = _('Error decoding signature.')# 被篡改了
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()# 不知名的错误

        user = self.authenticate_credentials(payload)

        return (user, jwt_value)

签发源码内的其他两个类

导入from rest_framework_jwt.views import obtain_jwt_token,refresh_jwt_token,verify_jwt_token

obtain_jwt_token = ObtainJSONWebToken.as_view()  # 获取token
refresh_jwt_token = RefreshJSONWebToken.as_view()  # 更新token
verify_jwt_token = VerifyJSONWebToken.as_view()  # 认证token

refresh_jwt_token 用法

配置文件
JWT_AUTH = {
    'JWT_ALLOW_REFRESH': True
}

路由
    path('refresh/', refresh_jwt_token)

DRF JWT认证(二)

verify_jwt_token 用法

path('verify/', verify_jwt_token),

DRF JWT认证(二)

自定义User表,签发token

普通写法,视图类写

上面我们写道,签发token是基于Django自带的 auth_user表签发,如果我们自定义User表该如何签发token,如下:

视图

自定义表签发token
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
from app01 import models
class UserView(ViewSetMixin,APIView):
    @action(methods=['POST'],detail=False)
    def login(self,request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = models.UserInfo.objects.filter(username=username,password=password).first()
        response_dict = {'code':None,'msg':None}
        # 源码copy错来使用
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        if user:
            '''
            签发token去源码copy过来使用
            '''
            # 载荷字典
            payload = jwt_payload_handler(user)
            print(payload)
            # {'user_id': 1, 'username': 'Hammer', 'exp': datetime.datetime(2022, 4, 10, 13, 13, 15, 363206), 'email': '123@qq.com', 'orig_iat': 1649596095}
            # 通过荷载得到token串
            token = jwt_encode_handler(payload)
            response_dict['code'] = 2000
            response_dict['msg'] = '登录成功'
            response_dict['token'] = token

        else:
            response_dict['code'] = 4001
            response_dict['msg'] = '登录失败,用户名或密码错误'
        return Response(response_dict)

模型

user表
class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    email = models.EmailField()

路由

from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',views.UserView,'user')

DRF JWT认证(二)

序列化类中写逻辑

源代码中的签名和验证是在序列化类中完成的,这是真正常用的。让我们使用这种方式来定制上面视图的验证逻辑,并将其写入序列化类,该类仅用于反序列化。这样我们就可以使用反序列化的字段验证功能来帮助我们验证(模型中的条件),但我们不做保存操作

[En]

Signing and verifying in the source code is done in the serialization class, which is really commonly used. Let’s use this way to customize and write the verification logic of the above view into the serialization class, which is only used for deserialization. In this way, we can use the field verification function of * deserialization * to help us verify (conditions in the model), * but we do not do save operations *

视图

from .serializer import UserInfoSerializer
class UserView(ViewSetMixin,APIView):
    @action(methods=['POST'],detail=False)
    def login(self,request):
        # 如果想获取什么这里可以实例化对象写入,比如request
        serializer = UserInfoSerializer(data=request.data, context={'request': request})
        response_dict = {'code':None,'msg':None}
        # 校验,局部钩子,全局钩子都校验完才算校验通过,走自己的校验规则
        if serializer.is_valid():
            # 从序列化器对象中获取token和username
           token = serializer.context.get('token')
           username = serializer.context.get('username')

           response_dict['code']=2000
           response_dict['msg']='登录成功'
           response_dict['token'] = token
           response_dict['username'] = username
        else:
            response_dict['code'] = 4001
            response_dict['msg'] = '登录失败,用户名或密码错误'

        return Response(response_dict)

序列化器

from rest_framework.exceptions import ValidationError

class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserInfo
        # 根据模型里的字段写
        fields = ['username', 'password']

    # 全局钩子
    def validate(self, attrs):
        # attrs是校验过的字段,这里利用
        username = attrs.get('username')
        password = attrs.get('password')
        user = UserInfo.objects.filter(username=username, password=password).first()

        from rest_framework_jwt.settings import api_settings
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

        if user:  # 登录成功

            payload = jwt_payload_handler(user)  # 得到荷载字典
            token = jwt_encode_handler(payload)  # 通过荷载得到token串
            # 将token放入context字典中
            self.context['token'] = token
            self.context['username'] = username
            # context是serializer和视图类沟通的桥梁
            print(self.context.get('request').method)
        else:  # 登录失败
            raise ValidationError('用户名或密码错误')
        return attrs

DRF JWT认证(二)

总结

需要我们注意的是, context只是我们定义的字典,比如上面写到的实例化序列化类中指定的context,那么就可以从序列化类打印出请求的方法, context是序列化类和视图类沟通的桥梁

自定义认证类

auth.py

import jwt
from django.utils.translation import ugettext as _
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.settings import api_settings
from .models import UserInfo

class JWTAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 第一步、取出传入的token,从请求头中取

        # 这里注意,获取的时候格式为:HTTP_请求头的key大写
        jwt_value = request.META.get('HTTP_TOKEN')
        jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
        # 验证token:验证是否过期,是否被篡改,是否有其他未知错误,从源码copy过来使用
        if jwt_value:
            try:
                payload = jwt_decode_handler(jwt_value)
            except jwt.ExpiredSignature:
                msg = _('Signature has expired.')
                raise exceptions.AuthenticationFailed(msg)
            except jwt.DecodeError:
                msg = _('Error decoding signature.')
                raise exceptions.AuthenticationFailed(msg)
            except jwt.InvalidTokenError:
                msg = _('Unknown Error.')
                raise exceptions.AuthenticationFailed(msg)

            # 第二部、通过payload获得当前登录用户,本质是用户信息通过base64编码到token串的第二段载荷中
            user = UserInfo.objects.filter(pk=payload['user_id']).first()
            # 返回user和token
            return (user, jwt_value)
        else:
            raise AuthenticationFailed('No token was detected')

视图

from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializer import BookSerializer
from .auth import JWTAuthentication
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    authentication_classes = [JWTAuthentication,]

序列化器

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

路由

from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('book',views.BookView,'book')

正常的情况

DRF JWT认证(二)

不携带token的情况

DRF JWT认证(二)

总结

  • 从请求头中获取token,格式是 HTTP_KEY,key要大写
  • 认证token串没有问题,返回用户信息从载荷中获取,本质是用户信息通过base64编码到token串的第二段载荷中,可以通过base64解码获取到用户信息

HTTP请求的数据在META中

HttpRequest.META

   一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例:
  取值:

    CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
    CONTENT_TYPE —— 请求的正文的MIME 类型。
    HTTP_ACCEPT —— 响应可接收的Content-Type。
    HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
    HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
    HTTP_HOST —— 客服端发送的HTTP Host 头部。
    HTTP_REFERER —— Referring 页面。
    HTTP_USER_AGENT —— 客户端的user-agent 字符串。
    QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
    REMOTE_ADDR —— 客户端的IP 地址。
    REMOTE_HOST —— 客户端的主机名。
    REMOTE_USER —— 服务器认证后的用户。
    REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。
    SERVER_NAME —— 服务器的主机名。
    SERVER_PORT —— 服务器的端口(是一个字符串)。
   从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时,
    都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_  前缀。
    所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。

*** 有错请指正,感谢~

Original: https://www.cnblogs.com/48xz/p/16128119.html
Author: HammerZe
Title: DRF JWT认证(二)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/500016/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • python数据分析透视表,定制你的分析计算需求

    透视表是一种可以对数据动态排布并且分类汇总的表格格式,在常用的python的数据分析非标准库pandas中体现为pivot_table模块。 pivot_table数据透视表可以灵…

    Python 2023年8月8日
    088
  • 常见的图像质量评估指标SSIM、PSNR、LPIPS

    现阶段针对有真实参考的图像生成任务,主要有三种质量评价指标,分别为两种人为设计的指标SSIM和PSNR,也包括深度学习网络抽取到的特征进行对比的LPIPS评价指标 一:结构相似性(…

    Python 2023年10月7日
    089
  • HDFS简介

    HDFS设计(Architecture) 原因:为了平衡数据的可靠性和写操作的花费 方法:默认副本数为3; 第一个副本在Client所处的节点上,若客户端在集群外,随机选一个; 第…

    Python 2023年6月12日
    093
  • 二叉树的遍历

    1.二叉树的遍历 二叉树主要有两种遍历方式: 深度优先遍历:先往深走,遇到叶子节点再往回走。 前序遍历(递归法,迭代法) 中左右 中序遍历(递归法,迭代法) 左中右 后序遍历(递归…

    Python 2023年10月12日
    066
  • scrapy爬虫命令

    1.创建项目:scrapy startproject test_scrapy 找目录:cd .\test_scrapy\ 3.创建一个模板: scrapy genspider py…

    Python 2023年10月1日
    080
  • django 接收post数据

    ### 回答1: 在 Django_中,可以通过编写一个视图函数来 _接收 POST_请求并保存 _数据。 首先,需要在urls.py文件中设置URL路径,并将其映射到视图函数。例…

    Python 2023年8月3日
    096
  • 编程练习-找零钱

    老村长在村口支了一个西瓜摊卖西瓜,规定每人只能买一个瓜,一个瓜5元。 村民们手里有5元,10元,20元币值的钱。 拿5元买瓜,不用找零。 拿10元买瓜,需要找零5元。 拿20元买瓜…

    Python 2023年6月3日
    0147
  • 非常详细的Pytest+Allure环境搭建过程–Windows版本

    关于selenium搭建,可以参考前一篇文章:selenium环境搭建-Windows版本 一、Pytest环境搭建 Pytest 是 python 的第三方单元测试框架,比自带 …

    Python 2023年9月9日
    0132
  • form表单内容序列化的两种方法

    form表单内容序列化 form表单自带两种方法serialize()方法和serializeArray()方法 1.serialize()方法 &#x63CF;&…

    Python 2023年6月9日
    096
  • 【django】使用多个app和多个数据库

    创建第二个app 在已经创建好初始app1的情况下,使用命令创建第二个app2: python manage.py startapp app2 将创建好的app2放入到app文件夹…

    Python 2023年8月4日
    082
  • feapder 与 scrapy 分布式爬虫速度对比

    测试用例为使用feapder的分布式爬虫与scrapy-redis爬虫,请求1万次百度,均为32并发1进程的情况下,计算耗时 运行feapder爬虫 python3 feapder…

    Python 2023年10月5日
    078
  • conda镜像源 及常用命令

    查看源 conda config –show-sources 添加仓库 conda config –add channels https://mirrors.tuna.tsin…

    Python 2023年9月8日
    072
  • phpMyAdmin给非技术人员一个查阅数据库的窗口

    背景 管理数据库的界面工具。 开发团队中一般有非技术背景人员,比如: 产品,功能测试人员; 对他们来说,可能安装数据库管理工具客户端都很麻烦,需要一款在线的网页工具能方便他们查阅数…

    Python 2023年10月12日
    095
  • KITTI数据集介绍

    本文为个人学习笔记,参考文献已经标注出。 kitti数据集主要分为以下几个文件夹。下面分别介绍。 一、标定校准文件 calib训练集存储为data_object_calib/tra…

    Python 2023年9月16日
    0152
  • 数字图像处理总结(冈萨雷斯版)

    数字图像处理前六章知识点总结 第一章:绪论 第二章:数字图像基础 第三章:灰度变换与空间滤波 第四章:频率域滤波 第五章:图像恢复与重建 第六章:彩色图像处理 第一章:绪论 1.数…

    Python 2023年9月7日
    0122
  • 【Python】和【Jupyter notebook】的正确安装方式

    Original: https://www.cnblogs.com/123456feng/p/16086692.htmlAuthor: 蚂蚁ailingTitle: 【Python…

    Python 2023年11月9日
    084
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球