DRF ~ day10 之 jwt原理解析、jwt开发流程、jwt介绍和快速使用、定制返回格式、自定义用户表,签发

本文主要是介绍DRF ~ day10 之 jwt原理解析、jwt开发流程、jwt介绍和快速使用、定制返回格式、自定义用户表,签发,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

jwt原理解析、jwt开发流程、jwt介绍和快速使用、定制返回格式、自定义用户表,签发


文章目录

  • jwt原理解析、jwt开发流程、jwt介绍和快速使用、定制返回格式、自定义用户表,签发
  • 一、jwt原理解析
    • 1.1、jwt的构成和工作原理
      • 1.1.1、JWT的构成
      • 1.1.2、header (头部)
      • 1.1.2、payload(载荷)
      • 1.1.3、signature(签证)
  • 二、jwt开发流程
  • 三、drf - jwt的快速使用
    • 3.1、jwt的快速使用的两种方法
    • 3.2、jwt的安装
    • 3.3、jwt的签发过程
    • 3.4、jwt的认证过程
  • 四、drf ~ jwt 定制返回格式
  • 五、自定义用户表和签发token
  • 六、drf-jwt自定义认证类
  • 七、drf-jwt的签发源码分析
  • 八、认证权限源码分析
  • 总结


一、jwt原理解析

在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

基于session的认证在这里插入图片描述
在这里插入图片描述

基于jwt的认证
在这里插入图片描述
在这里插入图片描述

1.1、jwt的构成和工作原理

1.1.1、JWT的构成

JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

1.1.2、header (头部)

jwt的头部承载两部分信息:

  • 声明类型:这里是jwt
  • 声明加密的算法:通常直接使用 HMAC SHA256

完整的头部信息就像下面这样的JSON:

'''
{
'type': 'JWT',
'alg': 'HS256'
}
'''

然后将头部进行base64编码,构成了第一部分。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

1.1.2、payload(载荷)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击。

公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义定义一个payload(载荷):

{"exp": "1234567890","name": "John Doe","user_id":99
}

然后将其进行base64编码,得到JWT的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

1.1.3、signature(签证)

JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

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

二、jwt开发流程

使用jwt认证的开发流程,就是两部分

  • 第一部分: 签发token的过程,登录做的
    用户携带用户名和密码,访问我,我们校验通过,生成token串,返回给前端
  • 第二部分:token认证过程,登录认证时使用,其实就是咱们之前讲的认证类,在认证类中完成对token的认证操作
    用户访问我们需要登陆后才能访问的接口,必须携带我们签发的token串(请求头)
    我们取出token,验证该token是否过期,是否被篡改,是否是伪造的
    如果正常,说明荷载中的数据,就是安全的,可以根据荷载中的用户id,查询出当前登录用户,放到request中即可

三、drf - jwt的快速使用

django + drf 框架中,使用jwt来做登录认证

3.1、jwt的快速使用的两种方法

  1. 使用第三方模块
    -django-rest-framework-jwt:https://github.com/jpadilla/django-rest-framework-jwt
    这个是老版本的,已经不维护了,在django框架3.x以上的版本已经不能使用了,只能3.x及以下版本使用

    -djangorestframework-simplejwt:https://github.com/jazzband/djangorestframework-simplejwt
    新的版本

  2. 我们可以自己封装
    参考链接: https://gitee.com/liuqingzheng/rbac_manager/tree/master/libs/lqz_jwt

3.2、jwt的安装

pip安装: pip3.9 install djangorestframework-jwt

3.3、jwt的签发过程

签发过程(是快速签发),必须是auth的user表(这个是别人已经帮您写好了)
写登录接口 -------》 基于auth的user表签发的

  • 路由层代码
from django.contrib import admin
from django.urls import path# 导入jwt签发的模块
from rest_framework_jwt.views import obtain_jwt_tokenurlpatterns = [path('admin/', admin.site.urls),# 这样写签发加登录接口就有了path('login/', obtain_jwt_token),
]
  • 创建超级用户
    在django的tools目录栏点击Run manage.py Task 后的创建创建超级用户
    createsuperuser
    在这里插入图片描述创建前
    在这里插入图片描述创建后
    在这里插入图片描述
  • 创建两个超级用户,密码一样,为什么结果不一样的分析?
    在这里插入图片描述
    补充:
    - 密码明文一样,每次加密后都不一样,如何做,动态加盐,动态的盐要保存,跟密码保存在一起
    - 有代码,有数据库,没有登录不进去的系统
  • postman展示(获取token成功)
    在这里插入图片描述
  • 可以根据返回的token解码出用户信息
import base64# "token":
# "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2ODUzNjEwMzksImVtYWlsIjoiIn0.JQr8OajKGCvT5o4QFca2aWKiK20D4KJPTJ6p15K_c8o"# 头部
# res = base64.b64decode('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9')
# print(res)  # b'{"typ":"JWT","alg":"HS256"}'# 载荷
# res = base64.b64decode('eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2ODUzNjEwMzksImVtYWlsIjoiIn0')
# 需要填充=
res = base64.b64decode('eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2ODUzNjEwMzksImVtYWlsIjoiIn0=')
print(res)   # b'{"user_id":1,"username":"root","exp":1685361039,"email":""}'

按上面的推导token出现了解决不了:
就是:如果token被获取,模拟发送请求,这个是解决不了的
但是:token不能篡改,不能伪造
如何保证token的安全性? 设置token的过期时间

以上代码实现的过程是使用auth的user表做登录,不需要写额外的代码了

3.4、jwt的认证过程

  • 路由层代码
from django.contrib import admin
from django.urls import path
from app01 import views
# 导入jwt签发的模块
from rest_framework_jwt.views import obtain_jwt_tokenurlpatterns = [path('admin/', admin.site.urls),# 这样写签发加登录接口就有了,# 登录接口就有了,并且可以签发token,如果使用auth的user表做登录,不需要写额外的代码了path('login/', obtain_jwt_token),# 写认证的接口path('books/', views.BookView.as_view()),
]
  • 认证类创建认证模块auth.py文件
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailedimport jwt
from rest_framework_jwt.settings import api_settings
from .models import Userjwt_decode_handler = api_settings.JWT_DECODE_HANDLERclass JWTAuthentication(BaseAuthentication):def authenticate(self, request):token = request.META.get('HTTP_TOKEN')# 背过# 校验token是否过期,合法,如果都通过,查询出当前用户,返回# 如果不通过,抛异常try:payload = jwt_decode_handler(token)# 如果认证通过,payload就可以认为是安全的,我们就可以使用user_id = payload.get('user_id')# 每个需要登录后,才能访问的接口,都会走这个认证类,一旦走到这个认证类,机会去数据库查询一次数据,会对数据造成压力?user = User.objects.get(pk=user_id)# 优化后的# user = User(username=payload.get('username'), id=user_id)# user = {'username':payload.get('username'), 'id':user_id}except jwt.ExpiredSignature:raise AuthenticationFailed('token过期')except jwt.DecodeError:raise AuthenticationFailed('解码失败')except jwt.InvalidTokenError:raise AuthenticationFailed('token认证异常')except Exception:raise AuthenticationFailed('token认证异常')return user, token
  • 视图层代码
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response# 设置必须登录后才能访问
class BookView(APIView):authentication_classes = [JSONWebTokenAuthentication]  # 认证类,drf-jwt提供的permission_classes = [IsAuthenticated]  # 权限类,drf提供的def get(self, request):return Response("你看到我了")
  • postman展示
    在这里插入图片描述

总结:访问的时候,要在请求头中携带,必须叫Authorization:jwt token串
认证规定,请求必须放在请求头中headers中写入
k必须写:Authorization v写:jwt 后加token

四、drf ~ jwt 定制返回格式

设置 登录签发token的接口,要返回code, msg,username, token等信息

对返回格式进行定制
1 写个函数,函数返回字典格式,返回的格式,会被序列化,前端看到
2 写的函数配置一下

新建一个py文件 jwt_response.py 文件

def common_response(token, user=None, request=None):return {'code': '100','msg': '登录成功','username': user.username,'token': token,}

配置文件(settings.py文件中)配置

JWT_AUTH = {'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.jwt_response.common_response',
}

postman展示
在这里插入图片描述

五、自定义用户表和签发token

  • 模型层代码
from django.db import models# Create your models here.class User(models.Model):username = models.CharField(max_length=32)password = models.CharField(max_length=64)age = models.IntegerField()email = models.CharField(max_length=64)
  • 路由层代码
from django.contrib import admin
from django.urls import path
from app01 import views
# 导入jwt签发的模块
from rest_framework_jwt.views import obtain_jwt_tokenurlpatterns = [path('admin/', admin.site.urls),# 自定义用户表,写接口,做的token签发path('login/', views.UserView.as_view({'post': 'login'})),
]
  • 视图层代码
# 自定义用户表,写登录接口,做token签发from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from .models import User# 导入签发模块
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLERclass UserView(ViewSet):def login(self, request):username = request.data.get('username')password = request.data.get('password')user = User.objects.filter(username=username, password=password).first()if user:# 登录成功,签发token# 签发token# 1、 通过user,获取payloadpayload = jwt_payload_handler(user)print(payload)# 2、通过payload,得到tokentoken = jwt_encode_handler(payload)return Response({'code': 100, 'msg': '登录成功', 'username': user.username, 'token': token})else:return Response({'code': 101, 'msg': '用户名或密码错误'})

*postman展示
在这里插入图片描述

六、drf-jwt自定义认证类

  • 创建认证类的文件auth.py写自定义认证类
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailedimport jwt
from rest_framework_jwt.settings import api_settings
from .models import Userjwt_decode_handler = api_settings.JWT_DECODE_HANDLERclass JWTAuthentication(BaseAuthentication):def authenticate(self, request):token = request.META.get('HTTP_TOKEN')# 背过# 校验token是否过期,合法,如果都通过,查询出当前用户,返回# 如果不通过,抛异常try:payload = jwt_decode_handler(token)# 如果认证通过,payload就可以认为是安全的,我们就可以使用user_id = payload.get('user_id')# 每个需要登录后,才能访问的接口,都会走这个认证类,一旦走到这个认证类,机会去数据库查询一次数据,会对数据造成压力?user = User.objects.get(pk=user_id)# 优化后的# user = User(username=payload.get('username'), id=user_id)# user = {'username':payload.get('username'), 'id':user_id}except jwt.ExpiredSignature:raise AuthenticationFailed('token过期')except jwt.DecodeError:raise AuthenticationFailed('解码失败')except jwt.InvalidTokenError:raise AuthenticationFailed('token认证异常')except Exception:raise AuthenticationFailed('token认证异常')return user, token
  • 视图层
from rest_framework.views import APIView
from rest_framework.response import Response
from .auth import JWTAuthenticationclass BookView(APIView):authentication_classes = [JWTAuthentication]def get(self, request):print(request.user.age)return Response("你看到我了")
  • 路由层
    path('books/', views.BookView.as_view()),

postman展示
在这里插入图片描述
这里的token是登录之后返回到前端的token

七、drf-jwt的签发源码分析

# from rest_framework_jwt.views import obtain_jwt_token
# obtain_jwt_token就是ObtainJSONWebToken.as_view()---》视图类.as_view()# 视图类
class ObtainJSONWebToken(JSONWebTokenAPIView):serializer_class = JSONWebTokenSerializer# 父类:JSONWebTokenAPIView
class JSONWebTokenAPIView(APIView):# 局部禁用掉权限和认证permission_classes = ()authentication_classes = ()def post(self, request, *args, **kwargs):# serializer=JSONWebTokenSerializer(data=request.data)serializer = self.get_serializer(data=request.data)# 调用序列化列的is_valid---》字段自己的校验规则,局部钩子和全局钩子# 读JSONWebTokenSerializer的局部或全局钩子if serializer.is_valid(): # 全局钩子在校验用户,生成token# 从序列化类中取出useruser = serializer.object.get('user') or request.user# 从序列化类中取出tokentoken = serializer.object.get('token')# 咱么定制返回格式的时候,写的就是这个函数response_data = jwt_response_payload_handler(token, user, request)response = Response(response_data)return responsereturn Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)# JSONWebTokenSerializer的全局钩子
class JSONWebTokenSerializer(Serializer):def validate(self, attrs):# attrs前端传入的,校验过后的数据 {username:lqz,password:lqz12345}credentials = {'username': attrs.get('usernme'),'password': attrs.get('password')}if all(credentials.values()): # 校验credentials中字典的value值是否都不为空# user=authenticate(username=前端传入的,password=前端传入的)# auth模块的用户名密码认证函数,可以传入用户名密码,去auth的user表中校验用户是否存在# 等同于:User.object.filter(username=username,password=加密后的密码).first()user = authenticate(**credentials)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),'user': user}else:msg = _('Unable to log in with provided credentials.')raise serializers.ValidationError(msg)else:msg = _('Must include "{username_field}" and "password".')msg = msg.format(username_field=self.username_field)raise serializers.ValidationError(msg)

八、认证权限源码分析

# from rest_framework_jwt.authentication import JSONWebTokenAuthentication
# JSONWebTokenAuthentication
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):def get_jwt_value(self, request):# auth=['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 Noneif smart_text(auth[0].lower()) != auth_header_prefix:return Noneif len(auth) == 1:msg = _('Invalid Authorization header. No credentials provided.')raise exceptions.AuthenticationFailed(msg)elif len(auth) > 2:msg = _('Invalid Authorization header. Credentials string ''should not contain spaces.')raise exceptions.AuthenticationFailed(msg)return auth[1] # token串# 父类中找:BaseJSONWebTokenAuthentication---》authenticate,找到了
class BaseJSONWebTokenAuthentication(BaseAuthentication):def authenticate(self, request):# 拿到前端传入的token,前端传入的样子是  jwt token串jwt_value = self.get_jwt_value(request)# 如果前端没传,返回None,request.user中就没有当前登录用户# 如果前端没有携带token,也能进入到视图类的方法中执行,控制不住登录用户# 所以咱们才加了个权限类,来做控制if jwt_value is None:return Nonetry: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()# 通过payload拿到当前登录用户user = self.authenticate_credentials(payload)return (user, jwt_value)# 如果用户不携带token,也能认证通过
# 所以我们必须加个权限类来限制class IsAuthenticated(BasePermission):def has_permission(self, request, view):return bool(request.user and request.user.is_authenticated)

总结

以上就是今天要讲的内容,本文仅仅简单介绍了jwt的使用。

这篇关于DRF ~ day10 之 jwt原理解析、jwt开发流程、jwt介绍和快速使用、定制返回格式、自定义用户表,签发的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/314340

相关文章

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Pydantic中Optional 和Union类型的使用

《Pydantic中Optional和Union类型的使用》本文主要介绍了Pydantic中Optional和Union类型的使用,这两者在处理可选字段和多类型字段时尤为重要,文中通过示例代码介绍的... 目录简介Optional 类型Union 类型Optional 和 Union 的组合总结简介Pyd

Vue3使用router,params传参为空问题

《Vue3使用router,params传参为空问题》:本文主要介绍Vue3使用router,params传参为空问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录vue3使用China编程router,params传参为空1.使用query方式传参2.使用 Histo

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

使用Python自建轻量级的HTTP调试工具

《使用Python自建轻量级的HTTP调试工具》这篇文章主要为大家详细介绍了如何使用Python自建一个轻量级的HTTP调试工具,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录一、为什么需要自建工具二、核心功能设计三、技术选型四、分步实现五、进阶优化技巧六、使用示例七、性能对比八、扩展方向建

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比