DRF之登录认证源码分析

【一】引入

【1】表模型

from django.db import models


# Create your models here.
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    password = models.CharField(max_length=64)


class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to=UserInfo, on_delete=models.CASCADE)

【2】视图类

from rest_framework.response import Response
from rest_framework.views import APIView

from authentications import LoginAgain
from user.models import UserToken


class TokenView(APIView):
    back_dict = {"code": 100, "msg": "", "result": ""}
    # 被 LoginAgain 管理的类才能访问
    authentication_classes = [LoginAgain, ]

    def get(self, request):
        token_obj = UserToken.objects.all()
        self.back_dict['msg'] = "请求数据成功"
        self.back_dict["result"] = token_obj.values()
        return Response(self.back_dict)

【3】路由

from django.contrib import admin
from django.urls import path
from user import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.TokenView.as_view()),
]

【4】自定义认证类

from django.shortcuts import render
from rest_framework.viewsets import ViewSet
from app01.models import UserToken, UserInfo
from rest_framework.response import Response
from rest_framework import status
import uuid
from rest_framework.decorators import action

# 登录接口
class UserView(ViewSet):
    back_dict = {"code": 100, "msg": "", "result": ""}

    @action(methods=["POST"], detail=False)
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = UserInfo.objects.filter(name=username, password=password).first()
        if user_obj:
            # 登陆成功
            # (1)生成随机字符串 - 永不重复的随机字符串(极小概率会重复)
            token = str(uuid.uuid4())
            self.back_dict["code"] = 100
            self.back_dict["msg"] = "登陆成功"
            self.back_dict["token"] = token
            # (2)把生成的token存储到数据库中,UserToken有值就更新,没有则新增
            UserToken.objects.update_or_create(user=user_obj, defaults={"token": token})
            return Response(self.back_dict, status=status.HTTP_200_OK)
        else:
            # 登陆失败
            self.back_dict["code"] = 101
            self.back_dict["msg"] = "登陆失败,用户名或密码错误"
            return Response(self.back_dict, status=status.HTTP_422_UNPROCESSABLE_ENTITY)


class LoginAgain(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')
        # 在数据库中取出token进行校验
        user_token = UserToken.objects.filter(token=token).first()
        if user_token:
            # 当前登录用户就是user
            user_obj = user_token.user
            return user_obj, token
        else:
            # 错误的token
            raise AuthenticationFailed("请先登录!谢谢!")

【5】发起请求

  • 登陆成功
    • 签发token
{
    "code": 100,
    "msg": "登陆成功",
    "result": "",
    "token": "d95c0a97-1e5f-4890-a98b-607786486116"
}

【二】ModelBackend源码分析

【1】源码

class ModelBackend(BaseBackend):
    """
    Authenticates against settings.AUTH_USER_MODEL.
    """

    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        if username is None or password is None:
            return
        try:
            user = UserModel._default_manager.get_by_natural_key(username)
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a nonexistent user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

【2】分析

class ModelBackend(BaseBackend):
    """
    # 认证 使用 settings 中的 用户模型 ,默认是使用 Django自带的 user表 , 也可以是使用自定义用户表
    Authenticates against settings.AUTH_USER_MODEL.
    """
	
    # 认证方法
    # username : 用户名 ---- 自定义用户表中的字段也必须叫 username
    # password : 密码 ---- 自定义用户表中的字段也必须叫 password
    def authenticate(self, request, username=None, password=None, **kwargs):
        # 判断用户名是否传入
        if username is None:
            # 如果没有传入用户名,就 获取用户表中 默认的 用户名字段 即 username
            username = kwargs.get(UserModel.USERNAME_FIELD)
        # 如果传入了用户名 或者 密码为空
        if username is None or password is None:
            # 返回空值
            return
        try:
            # 进行用户的校验
            # 在 用户模型表中 取到 用户名所对应的对象
            user = UserModel._default_manager.get_by_natural_key(username)
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a nonexistent user (#20760).
            # 调用用户模型的校验密码方法 (如果是默认的Django的user表,密码是加密的)
            UserModel().set_password(password)
        else:
            # 密码正确并且认证通过
            if user.check_password(password) and self.user_can_authenticate(user):
                # 返回校验过的用户
                return user
  • _default_manager
@property
def _default_manager(cls):
    return cls._meta.default_manager
  • default_manager
@cached_property

# "manager" 是指用于管理用户认证的对象
def default_manager(self):
    default_manager_name = self.default_manager_name
    
    # 检查是否存在default_manager_name属性,并且local_managers列表为空
    if not default_manager_name and not self.local_managers:
        # Get the first parent's default_manager_name if there's one.
        
        # 遍历模型的继承链
        for parent in self.model.mro()[1:]:
            
            # 找到第一个具有_meta属性的父类
            if hasattr(parent, '_meta'):
                
                # 获取其default_manager_name属性的值
                default_manager_name = parent._meta.default_manager_name
                break
	
    # 如果找到了default_manager_name
    if default_manager_name:
        try:
            
            # 从managers_map字典中获取对应的manager对象
            return self.managers_map[default_manager_name]
        except KeyError:
            
            # 表示没有名为default_manager_name的manager
            raise ValueError(
                "%s has no manager named %r" % (
                    self.object_name,
                    default_manager_name,
                )
            )
	
    # 如果没有找到default_manager_name并且存在managers列表
    # 则返回managers列表中的第一个manager对象作为默认manager。
    if self.managers:
        return self.managers[0]

【3】自定义认证类

# 导入 auth 模块的 ModelBackend 类
from django.contrib.auth.backends import ModelBackend
# 导入自定义用户模型
from DreamShopApi.apps.user.models import User
# 导入 Q 查询
from django.db.models import Q
# 导入Django自带的序列化器
from rest_framework import serializers

# 自定义认证类
class Authentice(ModelBackend):
    '''自定义认证类'''
	
    # 重写 authenticate 方法 ,自定义认证逻辑
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            # 利用自定义模型类查询用户是否存在
            user = User.objects.get(Q(username=username) | Q(phone=username) | Q(email=username))
        except Exception as e:
            # 捕获异常并抛出
            raise serializers.ValidationError({"errors": "未找到用户!"})
        else:
            # 校验密码是否正确
            if user.check_password(password):
                # 密码正确 返回认证用户
                return user
            else:
                # 抛出异常 , 用户密码不正确
                raise serializers.ValidationError({"errors": "密码错误!"})

【三】BaseAuthentication源码分析

【1】源码

class BaseAuthentication:
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response, or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        pass

【2】源码分析

class BaseAuthentication:
    """
    # 所有身份验证类都应扩展BaseAuthentication。
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        # 对请求进行身份验证并返回一个二元组(user,token)。
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """
        # 返回要用作`WWW-Authenticate值的字符串
        Return a string to be used as the value of the `WWW-Authenticate`
        # “401 Unauthenticated”响应中的标头
        header in a `401 Unauthenticated` response, or `None` if the
        # 身份验证方案应返回“403拒绝权限”响应
        authentication scheme should return `403 Permission Denied` responses.
        """
        pass

【四】JWTAuthentication源码分析

from rest_framework_simplejwt.authentication import JWTAuthentication

【1】源码分析

  • 继承基类并重写方法
class JWTAuthentication(authentication.BaseAuthentication):
    """
    # 通过JSON web验证请求的身份验证插件
    An authentication plugin that authenticates requests through a JSON web
    # 请求标头中提供的令牌。
    token provided in a request header.
    """
	
    # 身份验证领域
    www_authenticate_realm = "api"
    # 接受的前端数据类型为json格式
    media_type = "application/json"
	
    # 初始化方法
    def __init__(self, *args, **kwargs):
        # 调用父类的初识化方法
        super().__init__(*args, **kwargs)
        # 获取用户的默认用户模型类
        self.user_model = get_user_model()
	
    # 重写了 authenticate 方法
    def authenticate(self, request):
        # 获取请求头
        header = self.get_header(request)
        # 如果没有携带请求头
        if header is None:
            # 返回 None
            return None
		
        # 获取到用户携带的 token 字符串
        raw_token = self.get_raw_token(header)
        # 如果没有携带token
        if raw_token is None:
            # 返回 None 
            return None
		
        # 获取到校验过后的 token 字符串
        validated_token = self.get_validated_token(raw_token)
		
        # 返回已经校验过token的认证用户和token字符串
        # 这里对应了 基类 中必须返回一个 用户对象和 token 字符串的规定
        return self.get_user(validated_token), validated_token
	
    # 获取到用户携带的请求头的身份验证领域
    def authenticate_header(self, request):
        
        # 返回携带的请求头中的身份验证领域
        return '{} realm="{}"'.format(
            AUTH_HEADER_TYPES[0],
            self.www_authenticate_realm,
        )
	
    # 校验获取到请求头
    def get_header(self, request):
        """
        #从给定的请求头中获取json web token 字符串
        Extracts the header containing the JSON web token from the given
        request.
        """
        # 根据配置文件中的用户头名从请求对象的元数据中获取到对应的值
        # 默认是    
        # "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
        # 即在请求头中应该携带 Authorization 作为token字符串的键
        header = request.META.get(api_settings.AUTH_HEADER_NAME)
		
        # 判断获取到的数据是不是字符串
        if isinstance(header, str):
            # Work around django test client oddness
            # 将请求头转码
            header = header.encode(HTTP_HEADER_ENCODING)
		
        # 返回请求头
        return header
	
    # 取出请求头中的 token字符串
    def get_raw_token(self, header):
        """
        # 从给定的“授权”中提取未验证的JSON web令牌
        Extracts an unvalidated JSON web token from the given "Authorization"
        header value.
        """
        # 将请求头切分
        parts = header.split()
		
        # 判断切分后的长度是否为 0 
        if len(parts) == 0:
            # 长度 0 则必奥谢携带了空的 AUTHORIZATION
            # Empty AUTHORIZATION header sent
            return None
		
        # 如果 切出来的第一个参数不属于规定的参数内
        # AUTH_HEADER_TYPE_BYTES = {h.encode(HTTP_HEADER_ENCODING) for h in AUTH_HEADER_TYPES}
        if parts[0] not in AUTH_HEADER_TYPE_BYTES:
            
            # 假设标头不包含JSON web令牌
            # Assume the header does not contain a JSON web token
            # 返回空
            return None
		
        # 如果获取到的 值 大于 2 
        if len(parts) != 2:
            
            # 抛出异常 , 提示必须携带两个空格
            # 这不也就相当于我们在携带请求头是的格式是
            # key : Authorization
            # value : jwt  ... 
            # jwt 和 真的 token之间要有两个空格隔开
            raise AuthenticationFailed(
                _("Authorization header must contain two space-delimited values"),
                code="bad_authorization_header",
            )
		
        # 都正常,则返回真的token字符串
        return parts[1]
	
    # 获取到已经校验过的 token 令牌
    def get_validated_token(self, raw_token):
        """
        # 验证编码的JSON web令牌并返回经过验证的令牌
        Validates an encoded JSON web token and returns a validated token
        wrapper object.
        """
        
        # 
        messages = []
        
        # 遍历视图类中的每一个认证类
        for AuthToken in api_settings.AUTH_TOKEN_CLASSES:
            try:
                
                # 去每一个认证类内部校验 token
                return AuthToken(raw_token)
            except TokenError as e:
                # 如果 token 有错 则抛出异常
                messages.append(
                    {
                        "token_class": AuthToken.__name__,
                        "token_type": AuthToken.token_type,
                        "message": e.args[0],
                    }
                )
		
        # 捕获异常
        raise InvalidToken(
            # 不属于校验过的任意token类型
            {
                "detail": _("Given token not valid for any token type"),
                "messages": messages,
            }
        )
	
    # 获取到用户信息
    def get_user(self, validated_token):
        """
        Attempts to find and return a user using the given validated token.
        """
        try:
            # "validated_token" 是一个被验证的令牌或身份验证的结果
            # "api_settings.USER_ID_CLAIM" 则表示用户ID在验证令牌中的声明字段
            user_id = validated_token[api_settings.USER_ID_CLAIM]
        except KeyError:
            # 令牌不包含可识别的用户标识 --- token 认证没通过
            raise InvalidToken(_("Token contained no recognizable user identification"))

        try:
            # 根据用户ID 获取到用户对象
            user = self.user_model.objects.get(**{api_settings.USER_ID_FIELD: user_id})
        except self.user_model.DoesNotExist:
            
            # 抛出异常,没有找到对应的用户
            raise AuthenticationFailed(_("User not found"), code="user_not_found")
		
        # 判断用户状态是否活跃
        if not user.is_active:
            # 抛出异常  用户 已被冻结
            raise AuthenticationFailed(_("User is inactive"), code="user_inactive")
		
        # 返回查找的用户对象
        return user


# 基于上述 JWTAuthentication 认证类的扩展类
class JWTStatelessUserAuthentication(JWTAuthentication):
    """
    # 通过JSON web验证请求的身份验证插件
    An authentication plugin that authenticates requests through a JSON web
    # 在不执行数据库查找以获得用户实例的情况下在请求标头中提供的令牌。
    token provided in a request header without performing a database lookup to obtain a user instance.
    """
	
    # 获取到用户对象
    def get_user(self, validated_token):
        """
        # 基于已经签发认证过的对象,返回一个用户对象
        Returns a stateless user object which is backed by the given validated
        token.
        """
        # 验证令牌中是否包含了用户识别信息
        if api_settings.USER_ID_CLAIM not in validated_token:
            # #TokenUser类假定令牌将具有可识别的用户
            # The TokenUser class assumes tokens will have a recognizable user
            # identifier claim.
            # 抛出异常
            # 令牌不包含可识别的用户标识
            raise InvalidToken(_("Token contained no recognizable user identification"))
		
        #如果验证令牌中包含了用户识别信息
        # 那么函数会使用该信息来创建一个用户对象(api_settings.TOKEN_USER_CLASS(validated_token)),并将其返回
        return api_settings.TOKEN_USER_CLASS(validated_token)


JWTTokenUserAuthentication = JWTStatelessUserAuthentication


def default_user_authentication_rule(user):
    # #在Django 1.10之前,非活动用户可以通过
    #默认`ModelBackend`。从Django 1.10开始`
    #阻止非活动用户进行身份验证。应用程序设计者仍然可以
    #允许非活动用户通过选择新的进行身份验证
    #`AllowAllUsersModelBackend`。但是,我们明确禁止不活动
    #阻止用户进行身份验证以强制执行合理的策略并提供
    #与旧Django版本的合理向后兼容性。
    # Prior to Django 1.10, inactive users could be authenticated with the
    # default `ModelBackend`.  As of Django 1.10, the `ModelBackend`
    # prevents inactive users from authenticating.  App designers can still
    # allow inactive users to authenticate by opting for the new
    # `AllowAllUsersModelBackend`.  However, we explicitly prevent inactive
    # users from authenticating to enforce a reasonable policy and provide
    # sensible backwards compatibility with older Django versions.
    return user is not None and user.is_active

【五】SessionAuthentication源码分析

from rest_framework.authentication import SessionAuthentication

【1】源码

class SessionAuthentication(BaseAuthentication):
    """
    Use Django's session framework for authentication.
    """

    def authenticate(self, request):
        """
        Returns a `User` if the request session currently has a logged in user.
        Otherwise returns `None`.
        """

        # Get the session-based user from the underlying HttpRequest object
        user = getattr(request._request, 'user', None)

        # Unauthenticated, CSRF validation not required
        if not user or not user.is_active:
            return None

        self.enforce_csrf(request)

        # CSRF passed with authenticated user
        return (user, None)

    def enforce_csrf(self, request):
        """
        Enforce CSRF validation for session based authentication.
        """
        def dummy_get_response(request):  # pragma: no cover
            return None

        check = CSRFCheck(dummy_get_response)
        # populates request.META['CSRF_COOKIE'], which is used in process_view()
        check.process_request(request)
        reason = check.process_view(request, None, (), {})
        if reason:
            # CSRF failed, bail with explicit error message
            raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)

【2】源码分析

class SessionAuthentication(BaseAuthentication):
    """
    # 使用Django自带的默认框架进行 session 认证
    Use Django's session framework for authentication.
    """

    def authenticate(self, request):
        """
        # 如果用户 的 session 正确 则返回一个认证后的用户
        Returns a `User` if the request session currently has a logged in user.
        Otherwise returns `None`.
        """
		
        # 从底层HttpRequest对象获取基于会话的用户
        # Get the session-based user from the underlying HttpRequest object
        # 从request对象中获取到 用户对象
        user = getattr(request._request, 'user', None)

        # Unauthenticated, CSRF validation not required
        # 如果没有找到用户或者用户被冻结
        if not user or not user.is_active:
            # 返回空
            return None
		
        # 强制 CSRF 认证
        self.enforce_csrf(request)
		
        # Csrf 认证通过的认证用户
        # CSRF passed with authenticated user
        return (user, None)
	
    # 强制启动csrf认证
    def enforce_csrf(self, request):
        """
        # 对基于会话的身份验证强制执行CSRF验证。
        Enforce CSRF validation for session based authentication.
        """
        # 定义了一个名为dummy_get_response的内部函数,该函数用于模拟响应。
        def dummy_get_response(request):  # pragma: no cover
            return None
		
        # 接下来,创建了一个CSRFCheck的实例,将dummy_get_response作为参数传递进去。CSRFCheck是用于检查CSRF的类。
        check = CSRFCheck(dummy_get_response)
        	
        # 调用check.process_request(request)来填充request.META['CSRF_COOKIE'],该值在process_view()方法中使用。
        # populates request.META['CSRF_COOKIE'], which is used in process_view()
        check.process_request(request)
        
        # 调用check.process_view(request, None, (), {})进行CSRF验证。
        # 如果验证失败,将会抛出exceptions.PermissionDenied异常,其中包含了失败的原因
        reason = check.process_view(request, None, (), {})
        if reason:
            # CSRF验证失败
            # CSRF failed, bail with explicit error message
            raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
posted @ 2023-09-18 17:12  Chimengmeng  阅读(93)  评论(0编辑  收藏  举报
/* */