rest_framework_django学习笔记三(异常、登录认证)

rest_framework_django学习笔记三(异常、登录认证)

一、异常

REST framework 定义的异常

异常名称 说明
APIException 所有异常的父类
ParseError 解析错误
AuthenticationFailed 认证失败
NotAuthenticated 尚未认证
PermissionDenied 权限拒绝
NotFound 未找到
MethodNotAllowed 请求方式不支持
NotAcceptable 要获取的数据格式不支持
Throttled 超过限流次数
ValidationError 校验失败

1、自定义异常

REST framework 提供了异常处理,我们可以自定义异常处理函数

from  rest_framework.views import exception_handler
def custom_exception_handler(exc,context):
	# 先调用 REST framework 默认的异常处理方法获得标准错误响应对象
	response = exception_handler(exc,context)
	# 补充自定义的异常处理
	if response is not None:
		response.data['status_code'] = response.status_code
	return response

在配置文件中声明自定义的异常处理

REST_FRAMEWORK = {
    "EXCEPTION_HANDLER": "demo_app.utils.custom_exception_handler",  # 自定义的异常处理
}

2、补充异常处理

from  rest_framework import status
from django.db import DatabaseError
from rest_framework.response import Response
def custom_exception_handler(exc,context):
	# 先调用 REST framework 默认的异常处理方法获得标准错误响应对象
	response = exception_handler(exc,context)
	# 补充自定义的异常处理
	if response is None:
		view = context['view']
		if isinstance(exc,DatabaseError):
			print('[%s]:%s'%(view,exc))
			response = Response({'detail':'服务器错误'},status=status.HTTP_500_INTERNAL_SERVER_ERROR)
	return response

3、统一异常拦截

REST_FRAMEWORK = {
    "EXCEPTION_HANDLER": "demo_app.exception.CustomExceptionHandler",  # 自定义的异常处理
}

统一响应

# json_response.py
# -*- coding: utf-8 -*-
from rest_framework.response import Response
class SuccessResponse(Response):
    """
    标准响应成功的返回, SuccessResponse(data)或者SuccessResponse(data=data)
    (1)默认code返回2000, 不支持指定其他返回码
    """
    def __init__(self, data=None, msg='success', status=None, template_name=None, headers=None, exception=False,
                 content_type=None,page=1,limit=1,total=1):
        std_data = {
            "code": 200,
            "data": {
                "page": page,
                "limit": limit,
                "total": total,
                "data": data
            },
            "msg": msg
        }
        super().__init__(std_data, status, template_name, headers, exception, content_type)


class DetailResponse(Response):
    """
    不包含分页信息的接口返回,主要用于单条数据查询
    """
    def __init__(self, data=None,
                 msg='success',
                 status=None,
                 template_name=None,
                 headers=None,
                 exception=False,
                 content_type=None,):
        std_data = {
            "code": 200,
            "data": data,
            "msg": msg
        }
        super().__init__(std_data, status, template_name, headers, exception, content_type)


class ErrorResponse(Response):
    """
    标准响应错误的返回,ErrorResponse(msg='xxx')
    """
    def __init__(self, data=None, msg='error', code=500, status=None, template_name=None, headers=None,
                 exception=False, content_type=None):
        std_data = {
            "code": code,
            "data": data,
            "msg": msg
        }
        super().__init__(std_data, status, template_name, headers, exception, content_type)

自定义异常处理

# -*- coding: utf-8 -*-
# exception
"""自定义异常处理"""
import logging
import traceback

from django.db.models import ProtectedError, RestrictedError
from django.http import Http404
from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed, PermissionDenied
from rest_framework.status import HTTP_407_PROXY_AUTHENTICATION_REQUIRED, HTTP_401_UNAUTHORIZED
from rest_framework.views import set_rollback, exception_handler
from .json_response import ErrorResponse
logger = logging.getLogger(__name__)
def CustomExceptionHandler(ex, context):
    """
    统一异常拦截处理
    目的:(1)取消所有的500异常响应,统一响应为标准错误返回
        (2)准确显示错误信息
    :param ex:
    :param context:
    :return:
    """
    msg = ""
    code = 999
    # 调用默认的异常处理函数
    response = exception_handler(ex, context)
    if isinstance(ex, AuthenticationFailed):
        # 如果是身份验证错误
        if response and response.data.get("detail") == "Given token not valid for any token type":
            code = 401
            msg = ex.detail
        elif response and response.data.get("detail") == "Token is blacklisted":
            # token在黑名单
            return ErrorResponse(status=HTTP_401_UNAUTHORIZED)
        else:
            code = 401
            msg = ex.detail
    elif isinstance(ex, Http404):
        code = 400
        msg = "接口地址不正确"
    elif isinstance(ex, DRFAPIException):
        set_rollback()
        msg = ex.detail
        if isinstance(ex, PermissionDenied):
            msg = f'{msg} ({context["request"].method}: {context["request"].path})'
        if isinstance(msg, dict):
            for k, v in msg.items():
                for i in v:
                    msg = "%s:%s" % (k, i)
    elif isinstance(ex, (ProtectedError, RestrictedError)):
        set_rollback()
        msg = "无法删除:该条数据与其他数据有相关绑定"
    # elif isinstance(ex, DatabaseError):
    #     set_rollback()
    #     msg = "接口服务器异常,请联系管理员"
    elif isinstance(ex, Exception):
        logger.exception(traceback.format_exc())
        msg = str(ex)
    return ErrorResponse(msg=msg, code=code)

二、rest_framework 认证

1、全局

可以在配置文件中配置全局默认的认证方案

# settings.py
REST_FRAMEWORK = {
    "DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S",  # 日期时间格式配置
    "DATE_FORMAT": "%Y-%m-%d",
    "DEFAULT_AUTHENTICATION_CLASSES": (
        # "rest_framework_simplejwt.authentication.JWTAuthentication",# JWT 认证 # pip install djangorestframework-simplejwt
        "rest_framework.authentication.BasicAuthentication", # 基础认证
        "rest_framework.authentication.SessionAuthentication", # session 认证
    )
}

2、局部

也可以在视图中通过设置 authentication_classess 属性来设置 局部认证

from django.db.models import Q
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
from rest_framework.authentication import SessionAuthentication,BasicAuthentication
from .serializers import BookSerializer
from .models import Book
from rest_framework.response import Response
class BookModelViewSet(ModelViewSet):
	authentication_classes = (SessionAuthentication,BasicAuthentication)
    ...

认证失败会有两种可能的返回值

  • 401 Unauthorized 未认证
  • 403 Permission Denied 权限被禁止

三、JWT 认证

1、安装包

pip install djangorestframework-simplejwt

2、settings.py配置

# settings.py
INSTALLED_APPS = [
    # ...
    'rest_framework_simplejwt',
    # 下面这个app用于刷新refresh_token后,将旧的加到到blacklist时使用
    'rest_framework_simplejwt.token_blacklist'
    # ...
]
# ================================================= #
# ***************** REST_FRAMEWORK配置 ************ #
# ================================================= #
REST_FRAMEWORK = {
    ...
    "DEFAULT_AUTHENTICATION_CLASSES": (
        ...
        "rest_framework_simplejwt.authentication.JWTAuthentication",# JWT 认证 # pip install djangorestframework-simplejwt
    )
}

# ================================================= #
# ****************** simplejwt配置 ***************** #
# ================================================= #
# 选择自己需要配置即可
from datetime import timedelta
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': 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),
    # 用于生成访问令牌和刷新令牌的序列化器。
    "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",
}

# ================================================= #
# ****************** simplejwt配置 ***************** #
# ================================================= #
# 我选择的
from datetime import timedelta
SIMPLE_JWT = {
    # token有效时长
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
    # token刷新后的有效时间
    "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
    # 设置前缀
    "AUTH_HEADER_TYPES": ("JWT",),# 
    # 是否自动刷新Refresh Token
    "ROTATE_REFRESH_TOKENS": True,
    # 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为False,则旧的刷新令牌仍然可以用于获取新的访问令牌。需要将'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
    'BLACKLIST_AFTER_ROTATION': True,
    #  用于指定可以使用的令牌类。默认为"rest_framework_simplejwt.tokens.AccessToken"
    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
}

执行数据库迁移:

python manage.py  makemigrations
python manage.py  migrate

注意1 AUTH_HEADER_TYPES:

当配置了 AUTH_HEADER_TYPES 指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer";在使用 postman和其他的一些自带的认证中会在请求同中增加Bearer + token 字符串。在自定义Header 中可以自己进行增加内容。如我使用的是JWT开头的那么在 postman中

rest_framework_django学习笔记三(异常、登录认证)_第1张图片

源码查看

每次访问该视图时,都会调用**JSONWebTokenAuthentication.authenticate** 进行认证.

from rest_framework_simplejwt.authentication import JWTAuthentication
user, tokrn = JWTAuthentication().authenticate(request)

在源码 authenticate 方法中获取

rest_framework_django学习笔记三(异常、登录认证)_第2张图片

当不匹配时携带请求头会出现: 身份认证信息未提供。

rest_framework_django学习笔记三(异常、登录认证)_第3张图片

rest_framework_django学习笔记三(异常、登录认证)_第4张图片

3、提供获取和刷新token的url

from django.urls import path,include,re_path
from rest_framework.documentation import include_docs_urls

from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
    TokenVerifyView
)
urlpatterns = [
    # jwt 认证
    path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    # 下面这个是用来验证 token 的,根据需要进行配置
    path('token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]

rest_framework_django学习笔记三(异常、登录认证)_第5张图片

rest_framework_django学习笔记三(异常、登录认证)_第6张图片

4、自定义返回信息

该序列化器需继承TokenObtainPairSerializer类,可以在任意一个app中的seralizers.py中增加该自定义的序列化器,并重写了get_token()方法。在这个方法中,我们可以自定义Payload,将用户的信息添加到Token中。

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class LoginSerializer(TokenObtainPairSerializer):
    def get_token(cls, user):
        token = super().get_token(user)
        # 增加想要加到token中的信息
        token['username'] = user.username
        token['email'] = user.email
        return token
# 改写simple JWT提供的默认视图,在app01/views中新增一个视图,该视图需继承至默认的视图类TokenObtainPairView
class LoginView(TokenObtainPairView):
    """
    登录接口
    """
    serializer_class = LoginSerializer
    permission_classes = []
#urls.py
from demo_app.login import LoginView

urlpatterns = [
    # 登录
    path('login/',LoginView.as_view(),name='login'),
]

rest_framework_django学习笔记三(异常、登录认证)_第7张图片

rest_framework_django学习笔记三(异常、登录认证)_第8张图片

5、简单实现登录

5.1 账号或密码错误

TokenObtainSerializer 中登录登录失败提示:

rest_framework_django学习笔记三(异常、登录认证)_第9张图片

重写登陆提示:

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
from django.contrib.auth.models import User # 使用的是默认的django 用户
class LoginSerializer(TokenObtainPairSerializer):
    class Meta:
        model = User
        fields = "__all__"
        read_only_fields = ["id","username","email"]
    def get_token(cls,user):
        ''' 重写 获取 token中保存的内容 '''
        token = super().get_token(user)
        # 增加想要加到token中的信息
        token['username'] = user.username
        token['email'] = user.email
        return token
    # 登录失败提示
    default_error_messages = {"no_active_account": '账号/密码错误'}
    # 重写登录方法
    def validate(self,attrs):
      ''' 重写登录方法 '''
      # 获取传入的 用户名和密码
      username = self.initial_data.get("username", None)
      password = self.initial_data.get("password", None)
      print("username:",username,"\npassword:",password)
      data = super().validate(attrs)#获取 返回的 token内容
      return {"code": 200, "msg": "请求成功", "data": data}

class LoginView(TokenObtainPairView):
    """
    登录接口
    """
    serializer_class = LoginSerializer
    permission_classes = []

rest_framework_django学习笔记三(异常、登录认证)_第10张图片

5.2 增加统一返回

# -*- coding: utf-8 -*-
# 统一返回格式
from rest_framework.response import Response
class SuccessResponse(Response):
    """
    标准响应成功的返回, SuccessResponse(data)或者SuccessResponse(data=data)
    (1)默认code返回2000, 不支持指定其他返回码
    """

    def __init__(self, data=None, msg='success', status=None, template_name=None, headers=None, exception=False,
                 content_type=None,page=1,limit=1,total=1):
        std_data = {
            "code": 200,
            "data": {
                "page": page,
                "limit": limit,
                "total": total,
                "data": data
            },
            "msg": msg
        }
        super().__init__(std_data, status, template_name, headers, exception, content_type)


class DetailResponse(Response):
    """
    不包含分页信息的接口返回,主要用于单条数据查询
    (1)默认code返回 200, 不支持指定其他返回码
    """

    def __init__(self, data=None,
                 msg='success',
                 status=None,
                 template_name=None,
                 headers=None,
                 exception=False,
                 content_type=None,):
        std_data = {
            "code": 200,
            "data": data,
            "msg": msg
        }
        super().__init__(std_data, status, template_name, headers, exception, content_type)


class ErrorResponse(Response):
    """
    标准响应错误的返回,ErrorResponse(msg='xxx')
    (1)默认错误码返回 500, 也可以指定其他返回码:ErrorResponse(code=xxx)
    """

    def __init__(self, data=None, msg='error', code=500, status=None, template_name=None, headers=None,
                 exception=False, content_type=None):
        std_data = {
            "code": code,
            "data": data,
            "msg": msg
        }
        super().__init__(std_data, status, template_name, headers, exception, content_type)
# -*- coding: utf-8 -*-
from rest_framework.response import Response

class SuccessResponse(Response):
    """
    标准响应成功的返回, SuccessResponse(data)或者SuccessResponse(data=data)
    (1)默认code返回2000, 不支持指定其他返回码
    """

    def __init__(self, data=None, msg='success', status=None, template_name=None, headers=None, exception=False,
                 content_type=None,page=1,limit=1,total=1):
        std_data = {
            "code": 200,
            "data": {
                "page": page,
                "limit": limit,
                "total": total,
                "data": data
            },
            "msg": msg
        }
        super().__init__(std_data, status, template_name, headers, exception, content_type)


class DetailResponse(Response):
    """
    不包含分页信息的接口返回,主要用于单条数据查询
    (1)默认code返回 200, 不支持指定其他返回码
    """

    def __init__(self, data=None,
                 msg='success',
                 status=None,
                 template_name=None,
                 headers=None,
                 exception=False,
                 content_type=None,):
        std_data = {
            "code": 200,
            "data": data,
            "msg": msg
        }
        super().__init__(std_data, status, template_name, headers, exception, content_type)


class ErrorResponse(Response):
    """
    标准响应错误的返回,ErrorResponse(msg='xxx')
    (1)默认错误码返回 500, 也可以指定其他返回码:ErrorResponse(code=xxx)
    """

    def __init__(self, data=None, msg='error', code=500, status=None, template_name=None, headers=None,
                 exception=False, content_type=None):
        std_data = {
            "code": code,
            "data": data,
            "msg": msg
        }
        super().__init__(std_data, status, template_name, headers, exception, content_type)

异常处理

# -*- coding: utf-8 -*-
"""自定义异常处理"""
import logging
import traceback

from django.db.models import ProtectedError, RestrictedError
from django.http import Http404
from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed, PermissionDenied
from rest_framework.status import HTTP_407_PROXY_AUTHENTICATION_REQUIRED, HTTP_401_UNAUTHORIZED
from rest_framework.views import set_rollback, exception_handler
from .json_response import ErrorResponse
logger = logging.getLogger(__name__)
def CustomExceptionHandler(ex, context):
    """
    统一异常拦截处理
    目的:(1)取消所有的500异常响应,统一响应为标准错误返回
        (2)准确显示错误信息
    :param ex:
    :param context:
    :return:
    """
    msg = ""
    code = 500
    # 调用默认的异常处理函数
    response = exception_handler(ex, context)
    if isinstance(ex, AuthenticationFailed):
        # 如果是身份验证错误
        if response and response.data.get("detail") == "Given token not valid for any token type":
            code = 401
            msg = ex.detail
        elif response and response.data.get("detail") == "Token is blacklisted":
            # token在黑名单
            return ErrorResponse(status=HTTP_401_UNAUTHORIZED)
        else:
            code = 401
            msg = ex.detail
    elif isinstance(ex, Http404):
        code = 400
        msg = "接口地址不正确"
    elif isinstance(ex, DRFAPIException):
        set_rollback()
        msg = ex.detail
        if isinstance(ex, PermissionDenied):
            msg = f'{msg} ({context["request"].method}: {context["request"].path})'
        if isinstance(msg, dict):
            for k, v in msg.items():
                for i in v:
                    msg = "%s:%s" % (k, i)
    elif isinstance(ex, (ProtectedError, RestrictedError)):
        set_rollback()
        msg = "无法删除:该条数据与其他数据有相关绑定"
    # elif isinstance(ex, DatabaseError):
    #     set_rollback()
    #     msg = "接口服务器异常,请联系管理员"
    elif isinstance(ex, Exception):
        logger.exception(traceback.format_exc())
        msg = str(ex)
    return ErrorResponse(msg=msg, code=code)

登录

# coding=utf-8
'''
@date:2023/11/29 11:15
@mail:[email protected]
@Content:
'''
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
from django.contrib.auth.models import User
from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
from rest_framework.exceptions import APIException
from rest_framework.views import APIView
from django.contrib.auth.models import User
from .json_response import DetailResponse
class LoginSerializer(TokenObtainPairSerializer):
    class Meta:
        model = User
        fields = "__all__"
        read_only_fields = ["id","username","email"]
    def get_token(cls,user):
        ''' 重写 获取 token中保存的内容 '''
        token = super().get_token(user)
        # 增加想要加到token中的信息
        token['username'] = user.username
        token['email'] = user.email
        return token

    default_error_messages = {"no_active_account": '账号/密码错误'}
    # 重写登录方法
    def validate(self,attrs):
      ''' 重写登录方法 '''
      #
      username = self.initial_data.get("username", None)
      password = self.initial_data.get("password", None)
      print("username:",username,"\npassword:",password)
      # 可以 增加图片验证码验证,短信验证码验证等登录验证因子
      # if True:
      #     raise CustomValidationError("验证码不正确")
      # 获取 返回的 token内容
      data = super().validate(attrs)
      return {"code": 2000, "msg": "请求成功", "data": data}


# 改写simple JWT提供的默认视图,在app01/views中新增一个视图,该视图需继承至默认的视图类TokenObtainPairView
class LoginView(TokenObtainPairView):
    """
    登录接口
    """
    serializer_class = LoginSerializer
    permission_classes = []

class CustomValidationError(APIException):
    """
    继承并重写验证器返回的结果,避免暴露字段
    """
    def __init__(self, detail):
        self.detail = detail

rest_framework_django学习笔记三(异常、登录认证)_第11张图片

6、修改登录配置

settings.py 中修改登录配置

# ================================================= #
# ******************** 登录方式配置 ******************** #
# ================================================= #
AUTHENTICATION_BACKENDS = ["demo_app.backends.CustomBackend"]

重写等

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.hashers import check_password
from django.db.models import Q
from django.contrib.auth.models import User
from rest_framework.exceptions import APIException
class CustomValidationError(APIException):
    """
    继承并重写验证器返回的结果,避免暴露字段
    """
    def __init__(self, detail):
        self.detail = detail
class CustomBackend(ModelBackend):
    """
    重写认证登录
    """
    def authenticate(self, request, username=None, password=None, **kwargs):
        # 查询用户对象  可通过密码和邮箱登录
        user = User.objects.filter(Q(username=username) | Q(email=username)).first()
        # 判断 用户,密码, 是否是管理员
        if user and check_password(password, user.password) and user.is_staff:
            return user  # 验证通过返回对象 否则返回None
        else:
            raise CustomValidationError("登录用户账号或密码错误!")

rest_framework_django学习笔记三(异常、登录认证)_第12张图片

其他笔记

序列化器

视图路由

异常、登录认证

权限、限流

你可能感兴趣的:(django,学习,笔记)