首页 > 其他分享 >全局异常处理和jwt介绍与使用

全局异常处理和jwt介绍与使用

时间:2024-05-15 17:12:25浏览次数:22  
标签:get jwt rest framework token user import 全局 异常

全局异常处理和jwt介绍与使用

1. 全局异常处理

# APIView的dispatch的时候--》三大认证,视图类的方法中--》出了异常--》被异常捕获--》都会执行一个函数:
# 只要出了异常,都会执行 dispatch中的这句,这个函数
response = self.handle_exception(exc)


# handle_exception源码分析
 def handle_exception(self, exc):
        # 拿到一个函数内存地址--》配置文件中配置了
        # self.settings.EXCEPTION_HANDLER
        # 拿到这个函数:rest_framework.views.exception_handler
        exception_handler = self.get_exception_handler()
        # 执行这个函数,传入俩参数
        response = exception_handler(exc, context)
        # drf的 Response的对象
        return response
      
   	-rest_framework.views.exception_handler 逻辑分析
    	-判断异常是不是 drf内 exceptions.APIException的异常
        -如果是--》组装了Response返回了
        -如果不是--》返回了None
        -总结:默认drf只处理了自己的异常,django的异常,它没处理,直接前端能看到

1.1 自定义全局异常处理函数

from rest_framework.response import Response
from rest_framework.views import exception_handler


def common_exception_handler(exc, context):
    # 日志记录放这里即可:请求方式,请求的地址,客户端ip,用户id等等
    request = context.get('request')
    view = context.get('view')  # 哪个视图类出的错
    user_id = request.user.pk or '匿名用户'
    print(
        f'请求方式是:{request.method},请求地址是:{request.get_full_path()},客户端ip:{request.META.get("REMOTE_ADDR")},用户id:{user_id}')
    print(type(exc))
    # 1 返回 Response:说明是drf的异常      2 返回None:说明不是drf的异常
    response = exception_handler(exc, context)
    if response:
        if isinstance(response.data, dict):
            err = response.data.get('detail', '系统错误,请联系系统管理员')
        elif isinstance(response.data, list):
            err = response.data[0]
        else:
            err = '系统错误,请联系系统管理员'
        response = Response({'code': 998, 'msg': f'【drf的异常】:{err}'})
        pass
    else:
        if isinstance(exc, ZeroDivisionError):
            response = Response({'code': 991, 'msg': '不能除以0'})
        elif isinstance(exc, Exception):
            response = Response({'code': 992, 'msg': 'Exception错误'})
        else:
            # 表明是 非drf的异常
            err = str(exc)
            response = Response({'code': 999, 'msg': f'【django的异常】:{err}'})
    return response

2. jwt介绍

2.1 什么是jwt

 jwt 是什么?
	-全称是:json  web  token
    -是一种前后端登陆认证的方案,区别于之前的 cookie,session
    -它有 签发阶段--》登陆成功后签发
    -它有 认证阶段--》需要登陆后才能访问的接口,通过认证后,才能继续操作
    

    
token长什么样?
  典型三段式,用.分割
  每一段都适用base64编码
  第一段:头,header {"alg":"HS256","typ":"JWT"}
  	一般放:公司信息,加密方式,是jwt,一般是固定的
  第二段: 荷载 payload {"sub":"1234567890","name":"John Doe","admin":true}
  	一般放:登陆用户信息,用户名,用户id,过期时间,签发时间,是否是超级用户
  第三段:签名,signture 二进制数据
  	签发间断,通过头和荷载使用某种加密方式[HS256,md5,sha1]加密得到
    校验阶段,拿到token,取出第一和第二部分---》通过之前同样的加密方式加密得到新签名--》用新签名和第三段(传入的老签名)比较--》如果一样--》说明没有被篡改过--》信任--》如果不一样--》说明是被篡改了,或模拟生成的--》不能信任,返回错误
  

jwt实现了,不在后端存储,但是安全

示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

2.2 jwt原理 和session,cookie比较

  • cookie认证机制

  • session认证机制

  • jwt认证机制

2.3 jwt开发重点

  • 签发token --》登陆接口
  • 校验token --〉认证类

2.4 补充base64的编码和解码

# base64不是加密方案,只是编码方案,base64字符串的长度一定是4的倍数,如果不是=补齐
import json
import base64

# base64编码
userinfo = {'name': "cx", "age": 18}
# 转成json字符串
userinfo_str = json.dumps(userinfo)

res = base64.b64encode(userinfo_str.encode(encoding='utf-8'))
print(res)


# base64 解码
s = 'eyJuYW1lIjogImN4IiwgImFnZSI6IDE4fQ=='
res1 = base64.b64decode(s)
print(res1)

# base64的作用
	# 1.在网络中传输,字符串编码成base64
  # 2.图片使用base64编码
  # 3.jwt中使用

3. simple-jwt使用

3.1 快速体验

# 1.安装
pip install djangorestframework-simplejwt

# 2.登陆签发默认shying的是auth的user表,先手动创建一个用户登陆
createsuperuser

# 3.新增一个登陆接口,直接在路由设置即可
# 引入模块
from rest_framework_simplejwt.views import token_obtain_pair
# 填写路由
path('login/', token_obtain_pair),
# http://127.0.0.1:8001/app08/login/

# 4.登陆成功,拿到签发信息

# 5.访问只有登陆成功后才可访问的接口
# 引入模块校验模块
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
# 添加token的接口,必须登陆成功后才可添加
class UserTokenView(GenericViewSet,mixins.CreateModelMixin):
  # 两个模块缺一不可
  	authentication_classes = [JWTAuthentication,]
    permission_classes = [IsAuthenticated,]
    # 必须登陆才能新增
    queryset = models.UserToken.objects.all()
    serializer_class = UserTokenSerializer
    
# 6.访问路由,请求头一定要带Authorization参数,值是Bearer 空格 登陆成功签发的access

# 7. 未带参数提示 "detail": "身份认证信息未提供。

3.2 simple-jwt配置文件

# 签发成功后,返回两个token
# 1.refresh:无感刷新使用
# 2.access:咱们用的,access实现,获取新的access用
3.2.1 默认配置文件
# JWT配置
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # Access Token的有效期
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),  # Refresh Token的有效期
    
    # 对于大部分情况,设置以上两项就可以了,以下为默认配置项目,可根据需要进行调整
    
    # 是否自动刷新Refresh Token
    'ROTATE_REFRESH_TOKENS': False,  
    # 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为False,则旧的刷新令牌仍然可以用于获取新的访问令牌。需要将'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
    'BLACKLIST_AFTER_ROTATION': False,  
    'ALGORITHM': 'HS256',  # 加密算法
    'SIGNING_KEY': settings.SECRET_KEY,  # 签名密匙,这里使用Django的SECRET_KEY
    # 如为True,则在每次使用访问令牌进行身份验证时,更新用户最后登录时间
    "UPDATE_LAST_LOGIN": False, 
    # 用于验证JWT签名的密钥返回的内容。可以是字符串形式的密钥,也可以是一个字典。
    "VERIFYING_KEY": "",
    "AUDIENCE": None,# JWT中的"Audience"声明,用于指定该JWT的预期接收者。
    "ISSUER": None, # JWT中的"Issuer"声明,用于指定该JWT的发行者。
    "JSON_ENCODER": None, # 用于序列化JWT负载的JSON编码器。默认为Django的JSON编码器。
    "JWK_URL": None, # 包含公钥的URL,用于验证JWT签名。
    "LEEWAY": 0, # 允许的时钟偏差量,以秒为单位。用于在验证JWT的过期时间和生效时间时考虑时钟偏差。
    # 用于指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer"
    "AUTH_HEADER_TYPES": ("Bearer",), 
    # 包含JWT的HTTP请求头的名称。默认为"HTTP_AUTHORIZATION"
    "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", 
     # 用户模型中用作用户ID的字段。默认为"id"。
    "USER_ID_FIELD": "id",
     # JWT负载中包含用户ID的声明。默认为"user_id"。
    "USER_ID_CLAIM": "user_id",
    
    # 用于指定用户身份验证规则的函数或方法。默认使用Django的默认身份验证方法进行身份验证。
    "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
    #  用于指定可以使用的令牌类。默认为"rest_framework_simplejwt.tokens.AccessToken"。
    "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
    # JWT负载中包含令牌类型的声明。默认为"token_type"。
    "TOKEN_TYPE_CLAIM": "token_type",
    # 用于指定可以使用的用户模型类。默认为"rest_framework_simplejwt.models.TokenUser"。
    "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
    # JWT负载中包含JWT ID的声明。默认为"jti"。
    "JTI_CLAIM": "jti",
    # 在使用滑动令牌时,JWT负载中包含刷新令牌过期时间的声明。默认为"refresh_exp"。
    "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
    # 滑动令牌的生命周期。默认为5分钟。
    "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
    # 滑动令牌可以用于刷新的时间段。默认为1天。
    "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
    # 用于生成access和刷refresh的序列化器。
    "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
    # 用于刷新访问令牌的序列化器。默认
    "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
    # 用于验证令牌的序列化器。
    "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
    # 用于列出或撤销已失效JWT的序列化器。
    "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
    # 用于生成滑动令牌的序列化器。
    "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
    # 用于刷新滑动令牌的序列化器。
    "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}



# 我们项目中如何配置 settings.py中
from datetime import timedelta
SIMPLE_JWT ={
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # Access Token的有效期
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),  # Refresh Token的有效期
    "AUTH_HEADER_TYPES": ("TOKEN",),
}

3.3 定制登陆返回格式

# 1.写个序列化类,继承TokenObtainPairSerializer--》重新validata方法
class CommonTokenObtainSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        # 校验还用原来的--》校验用户名和密码
        # 校验通过:把校验通过的user放到self.user 中
        dic = super().validate(attrs)  
        data = {'code': 100,
                'msg': '登录成功',
                'username': self.user.username,
                'refresh': dic.get('refresh'),
                'access': dic.get('acces s')
                }
        return data
    
# 2.配置文件配置
SIMPLE_JWT ={
    'TOKEN_OBTAIN_SERIALIZER':'app01.serializer.CommonTokenObtainSerializer'
}

# 3.前端看到
{
    "code": 100,
    "msg": "登录成功",
    "username": "serein",
    "refresh": "",
    "access": ""
}

3.4 定制payload格式

# 1.写个序列化类,继承TokenObtainPairSerializer--》重新validata方法
class CommonTokenObtainSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        # super()--->代指父类对象--》
        # 对象调用类的绑定方法--->会自动把对象的类传入
        token = super().get_token(user)
        token['name'] = user.username
        return token
    def validate(self, attrs):
        dic = super().validate(attrs)  
        data = {'code': 100,
                'msg': '登录成功',
                'username': self.user.username,
                'refresh': dic.get('refresh'),
                'access': dic.get('access')
                }
        return data
    
# 2.配置文件配置
SIMPLE_JWT ={
    'TOKEN_OBTAIN_SERIALIZER':'app01.serializer.CommonTokenObtainSerializer'
}

# 3.前端看到
{
    "code": 100,
    "msg": "登录成功",
    "username": "lqz",
    "refresh": "",
    "access": ""
}

3.5 多方式登陆

# 1.目前所使用的是auth的user表,只能穿用户名密码进行校验
# 2.往往项目中大多使用手机号、用户名、邮箱+密码的形式进行校验
# 3.我们可以自定制登陆接口,进行签发认证,签发自己签发,认证继续调用simple-jwt的认证
3.5.1 路由
from django.contrib import admin
from django.db import router
from django.urls import path
from app01 import views
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
# http://127.0.0.1:8000/app01/jwt/login/
router.register('jwt', views.UserJWTView, 'jwt')
urlpatterns = [
]
urlpatterns += router.urls
3.5.2 视图类
from rest_framework.decorators import action
from rest_framework.response import Response

from rest_framework.viewsets import GenericViewSet


from .serializer import LoginJWTSerializer


class UserJWTView(GenericViewSet):
    serializer_class = LoginJWTSerializer

    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():  # 执行 三层认证
            # 校验通过:会把user,access和refresh都放到序列化类对象中--》返回给前端、
            # 现在在视图类中----》有个序列化类--》把视图类中变量给序列化类---》序列化类的变量给视图类--》借助于context给[字典]
            refresh = serializer.context.get('refresh')
            access = serializer.context.get('access')
            return Response({'code': 100, 'msg': '登录成功', 'refresh': refresh, 'access': access})
        else:
            return Response({'code': 101, 'msg': serializer.errors})

3.5.3 序列化类
import re

from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework_simplejwt.tokens import RefreshToken

from .models import UserInfo


class LoginJWTSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def _get_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')

        # 去数据查询用户,ysername可能是手机号,邮箱或者用户名,用整体提取查对应字段
        if re.match(r'^1[3-9][0-9]{9}$', username):
            user = UserInfo.objects.filter(mobile=username).first()
        elif re.match('^.+@.+$', username):
            user = UserInfo.objects.filter(email=username).first()
        else:
            user = UserInfo.objects.filter(username=username).first()

        # 校验密码
        if user and user.check_password(password):
            return user
        else:
            raise ValidationError('用户名或密码错误')

        # 全局钩子
    def validate(self, attrs):
        # 取出 手机号/用户名/邮箱+密码--》数据库校验--》校验通过签发 access和refresh,放到context中
        user = self._get_user(attrs)
        # 签发token--》通过user对象,签发token
        token = RefreshToken.for_user(user)
        self.context['access'] = str(token.access_token)
        self.context['refresh'] = str(token)
        return attrs  # 不返回不行:因为源码中校验了是否为空--》

3.5.4 前端请求

总结
# 1.校验数据,放到序列化类的 validate中,而不放在视图类的方法中乐
# 2.视图类和序列化类直接交互变量
	serializer.context
    
# 3.user.check_password  必须是auth的user表,校验密码使用它


# 4.attrs必须返回值,返回空报错

# 5.视图类的方法校验失败的else中:也要return Response
# 6.如何签发token
token = RefreshToken.for_user(user)
self.context['access'] = str(token.access_token)
self.context['refresh'] = str(token)

4.自定义用户表,手动签发和认证

  • 自定义用户表,不再扩写auth的user表
  • 自己写登陆签发的token和认证类

4.1 新建用户表

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    user_type=models.IntegerField(choices=((1,'注册用户'),(2,'普通管理员'),(3,'超级管理员')),default=1)
    @property
    def is_authenticated(self):
        return True

4.2 路由

from app02 import views
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('our_jwt', views.UserOurJWTView, 'our_jwt')
router.register('publish', views.PublishView, 'publish')

urlpatterns = [
]
urlpatterns += router.urls

4.3 视图类

from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from .serializer import LoginJWTSerializer


class UserOurJWTView(GenericViewSet):
    serializer_class = LoginJWTSerializer

    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            refresh = serializer.context.get('refresh')
            access = serializer.context.get('access')

            return Response({'code': 100, 'msg': '登录成功', 'refresh': refresh, 'access': access})
        else:
            return Response({'code': 101, 'msg': serializer.errors})


from .OurJWTAuth import JWTOurAuth


class PublishView(GenericViewSet):
  # 此处调用认证类校验
    authentication_classes = [JWTOurAuth]

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

4.4 序列化类

from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework_simplejwt.tokens import RefreshToken

from .models import User


class LoginJWTSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def _get_user(self, attrs):
        # 1 校验用户
        username = attrs.get('username')
        password = attrs.get('password')
        user = User.objects.filter(username=username, password=password).first()

        if user:
            return user
        else:
            raise ValidationError('用户名或密码错误')

    def validate(self, attrs):
        user = self._get_user(attrs)
        token = RefreshToken.for_user(user)
        self.context['access'] = str(token.access_token)
        self.context['refresh'] = str(token)
        return attrs

4.5 认证类

from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.authentication import JWTAuthentication
from .models import User


class JWTOurAuth(JWTAuthentication):
    def authenticate(self, request):
        # 获取请求头携带的token
        token = request.META.get('HTTP_AUTHORIZATION')
        if token:
            # 校验token,get_validated_token返回的就是可以信任的payload
            validated_token = self.get_validated_token(token)
            # 取user_id
            user_id = validated_token['user_id']
            # 查询用户
            user = User.objects.filter(pk=user_id).first()
            # 返回用户和token
            return user, token
        else:
            raise AuthenticationFailed('请携带登录信息')

4.6 前端请求

此处token用什么参数,取决于你认证类中取token时用的是那个字段

标签:get,jwt,rest,framework,token,user,import,全局,异常
From: https://www.cnblogs.com/Formerly/p/18194208

相关文章

  • ASP.NET Core的全局拦截器(在页面回发时,如果判断当前请求不合法,不执行OnPost处理器)
    ASP.NETCoreRazorPages中,我们可以在页面模型基类中重载OnPageHandlerExecuting方法。下面的例子中,BaseModel继承自PageModel,是所有页面模型的基类。推荐方案:在BaseModel.cs中,重载OnPageHandlerExecuting方法(看下面代码中的注释):publicoverridevoidOnPageHandlerExecuting......
  • H5 新增的全局属性
    属性名功能contenteditable表示元素可否被用户编辑,可选值:true、falsedraggable表示元素可以拖动,可选值:true、falsehidden隐藏元素和CSSdisplaynone的是一样的spelicheck拼写检查,可选值:true、falsecontextmenu规定元素上下文菜单,在用户点击元素时显示......
  • jwt揭秘(含源码示例和视频)
    jwt揭秘摘自:https://www.cnblogs.com/wupeiqi/p/11854573.html武沛齐博客JSONWebTokens,是一种开发的行业标准RFC7519,用于安全的表示双方之间的声明。目前,jwt广泛应用在系统的用户认证方面,特别是现在前后端分离项目。1.jwt认证流程在项目开发中,一般会按照上图所示......
  • EBS 可保留量(Quantity Available To Reserve) 异常
    EBS可保留量(QuantityAvailableToReserve)异常销售挑库因为可保留量不足失败: 库存管理下检查现有量,现有量正常,但是处理和可保留存在占用。查询待定事务处理和相关接口表,未发现待定数据。(tmp和interface)检查批次失效时间(R12.0BUG):物料无批次失效控制。(注:R12.0存在一个bu......
  • EBS 可保留量(Quantity Available To Reserve) 异常 -- 子库间的转移
    1、出现可保留量数量为0或者不不相等的情况 2、查看子库状态的生效状态 3、从新跑请求:保留接口管理器   4、最后可用量就出来了   ......
  • 【java】异常 (详解)
     在Java中,将程序执行过程中发生的不正常行为称为异常  比如以下几个异常1.算术异常 因为 0不能当被除数,所以报出了异常,这种异常就叫作算数异常 2.空指针异常  3.数组越界异常   4.在编译时就发现了异常 那有的异常在编译时就被发现,有的要在运行之......
  • 【python】异常获取
    【日期】2024/5/14【问题】代码异常获取【分析】1、在代码中获取异常,并打印堆栈try:Qt5QWindowIcon=MainWindow.child_window(title="FJDTrionModel",class_name="Qt5QWindowIcon")exceptExceptionase:traceback.print_exc()2、......
  • 记录一次Springboot Data Jdbc的autoWorkController异常
    Errorcreatingbeanwithname'autoWorkController':Unsatisfieddependencyexpressedthroughfield'jdbcRouteRepository':Errorcreatingbeanwithname'jdbcRouteRepository'definedincom.chint.infrastructure.repository.jdbc......
  • 连接mysql异常
    问题描述C#连接MySql时,System.Security.Authentication.AuthenticationException:调用SSPI失败,请参见内部异常。所用版本4.5.0原因分析:据查此问题因mysql数据库没有安装ssl证书导致。解决方案:连接字符串中加上“SslMode=none”,。stringconnectStr="server=127.0.0.1;U......
  • validation捕获异常
    好像需要在控制器启用校验才能捕获参数校验,即@RestController@Validated----------------------分隔符-----------------------------importjakarta.validation.ConstraintViolation;importjakarta.validation.ConstraintViolationException;importorg.springframework.......