DRF之登录认证源码分析
【一】引入
【1】表模型
| from django.db import models |
| |
| |
| |
| 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": ""} |
| |
| 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: |
| |
| |
| token = str(uuid.uuid4()) |
| self.back_dict["code"] = 100 |
| self.back_dict["msg"] = "登陆成功" |
| self.back_dict["token"] = token |
| |
| 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') |
| |
| user_token = UserToken.objects.filter(token=token).first() |
| if user_token: |
| |
| user_obj = user_token.user |
| return user_obj, token |
| else: |
| |
| raise AuthenticationFailed("请先登录!谢谢!") |
【5】发起请求
| { |
| "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: |
| |
| |
| 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. |
| """ |
| |
| |
| |
| |
| 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: |
| |
| |
| |
| UserModel().set_password(password) |
| else: |
| |
| if user.check_password(password) and self.user_can_authenticate(user): |
| |
| return user |
| @property |
| def _default_manager(cls): |
| return cls._meta.default_manager |
| @cached_property |
| |
| |
| def default_manager(self): |
| default_manager_name = self.default_manager_name |
| |
| |
| if not default_manager_name and not self.local_managers: |
| |
| |
| |
| for parent in self.model.mro()[1:]: |
| |
| |
| if hasattr(parent, '_meta'): |
| |
| |
| default_manager_name = parent._meta.default_manager_name |
| break |
| |
| |
| if default_manager_name: |
| try: |
| |
| |
| return self.managers_map[default_manager_name] |
| except KeyError: |
| |
| |
| raise ValueError( |
| "%s has no manager named %r" % ( |
| self.object_name, |
| default_manager_name, |
| ) |
| ) |
| |
| |
| |
| if self.managers: |
| return self.managers[0] |
【3】自定义认证类
| |
| from django.contrib.auth.backends import ModelBackend |
| |
| from DreamShopApi.apps.user.models import User |
| |
| from django.db.models import Q |
| |
| from rest_framework import serializers |
| |
| |
| class Authentice(ModelBackend): |
| '''自定义认证类''' |
| |
| |
| 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" |
| |
| media_type = "application/json" |
| |
| |
| def __init__(self, *args, **kwargs): |
| |
| super().__init__(*args, **kwargs) |
| |
| self.user_model = get_user_model() |
| |
| |
| def authenticate(self, request): |
| |
| header = self.get_header(request) |
| |
| if header is None: |
| |
| return None |
| |
| |
| raw_token = self.get_raw_token(header) |
| |
| if raw_token is None: |
| |
| return None |
| |
| |
| validated_token = self.get_validated_token(raw_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. |
| """ |
| |
| |
| |
| |
| header = request.META.get(api_settings.AUTH_HEADER_NAME) |
| |
| |
| if isinstance(header, str): |
| |
| |
| header = header.encode(HTTP_HEADER_ENCODING) |
| |
| |
| return header |
| |
| |
| def get_raw_token(self, header): |
| """ |
| # 从给定的“授权”中提取未验证的JSON web令牌 |
| Extracts an unvalidated JSON web token from the given "Authorization" |
| header value. |
| """ |
| |
| parts = header.split() |
| |
| |
| if len(parts) == 0: |
| |
| |
| return None |
| |
| |
| |
| if parts[0] not in AUTH_HEADER_TYPE_BYTES: |
| |
| |
| |
| |
| return None |
| |
| |
| if len(parts) != 2: |
| |
| |
| |
| |
| |
| |
| raise AuthenticationFailed( |
| _("Authorization header must contain two space-delimited values"), |
| code="bad_authorization_header", |
| ) |
| |
| |
| return parts[1] |
| |
| |
| 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: |
| |
| |
| return AuthToken(raw_token) |
| except TokenError as e: |
| |
| messages.append( |
| { |
| "token_class": AuthToken.__name__, |
| "token_type": AuthToken.token_type, |
| "message": e.args[0], |
| } |
| ) |
| |
| |
| raise InvalidToken( |
| |
| { |
| "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: |
| |
| |
| user_id = validated_token[api_settings.USER_ID_CLAIM] |
| except KeyError: |
| |
| raise InvalidToken(_("Token contained no recognizable user identification")) |
| |
| try: |
| |
| 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 |
| |
| |
| |
| 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: |
| |
| |
| |
| |
| |
| raise InvalidToken(_("Token contained no recognizable user identification")) |
| |
| |
| |
| return api_settings.TOKEN_USER_CLASS(validated_token) |
| |
| |
| JWTTokenUserAuthentication = JWTStatelessUserAuthentication |
| |
| |
| def default_user_authentication_rule(user): |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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`. |
| """ |
| |
| |
| user = getattr(request._request, 'user', None) |
| |
| |
| if not user or not user.is_active: |
| return None |
| |
| self.enforce_csrf(request) |
| |
| |
| return (user, None) |
| |
| def enforce_csrf(self, request): |
| """ |
| Enforce CSRF validation for session based authentication. |
| """ |
| def dummy_get_response(request): |
| return None |
| |
| check = CSRFCheck(dummy_get_response) |
| |
| check.process_request(request) |
| reason = check.process_view(request, None, (), {}) |
| if reason: |
| |
| 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`. |
| """ |
| |
| |
| |
| |
| user = getattr(request._request, 'user', None) |
| |
| |
| |
| if not user or not user.is_active: |
| |
| return None |
| |
| |
| self.enforce_csrf(request) |
| |
| |
| |
| return (user, None) |
| |
| |
| def enforce_csrf(self, request): |
| """ |
| # 对基于会话的身份验证强制执行CSRF验证。 |
| Enforce CSRF validation for session based authentication. |
| """ |
| |
| def dummy_get_response(request): |
| return None |
| |
| |
| check = CSRFCheck(dummy_get_response) |
| |
| |
| |
| check.process_request(request) |
| |
| |
| |
| reason = check.process_view(request, None, (), {}) |
| if reason: |
| |
| |
| raise exceptions.PermissionDenied('CSRF Failed: %s' % reason) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通