DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • Django

  • 第一次学drf

  • 第二次学drf

    • drfCBV
    • 认证
      • 快速使用 - 案例1
        • 实验结果
        • 关键代码
      • 常见示例 - 案例2
      • oop的继承
      • 源码流程
      • 常见示例 - 案例3
      • 综合案例 - 用户登陆+认证
        • 关键代码
        • 进行验证
      • 模拟
    • 权限
    • 限流
    • 版本
    • 解析器
    • 元类
    • 序列化使用
    • 序列化源码
    • 验证&源码
    • 序列化+验证
    • Serializer案例
    • Serializer总结
    • 分页
    • 视图+路由+过滤
    • 练习+跨域问题
    • 博客练习
    • 源码分析汇总
  • 温故知新

  • flask

  • 后端
  • 第二次学drf
DC
2023-11-16
目录

认证

AnonymousUser类
如果用户认证失败,或者未认证,那么request.user就是 AnonymousUser/匿名用户,它是AnonymousUser类的实例化对象.

让我回顾下,看到"认证",最起码要想到啥基本的.(以下就是大白话了)
写认证类,重写authenticate和authenticate_header方法..
在authenticate方法里进行验证,简单来说就是取token、验证token,正确返回元祖(user,token),错误抛出异常
另外,认证成功,后续的request中可通过request.user和request.auth取到user和token
多个认证类的authenticate方法返回None时的逻辑(两种情况:支持匿名访问 以及 必须认证登陆成功后才能访问).
    伪代码如下:
    for authenticator in [认证类,认证类,认证类]:
        user_auth_tuple = authenticator.authenticate()  # 此处抛出了异常也不会往下执行啦.
        if user_auth_tuple:
            return
    简单来说,执行每个认证类里的authenticate方法
      - 一旦某个认证类认证成功或者认证过程抛出异常导致认证失败,不会再管后续的认证类;
      - 只有返回None,才会继续往后执行下一个认证类里的authenticate方法;
      (简而言之,这么些个认证类,只要有一个认证成功或失败,后续的认证类就不用管了)
全局认证和局部认证的配置. -- 底层本质原理就是oop的继承

看那三个案例就很快明了啦.
案例1: 项目要开发3个接口,其中1个是无需登录就能访问的接口、其余2个是必须登录才能访问的接口.
案例2: 项目要开发100个接口,其中1个是无需登录就能访问的接口、其余99个是必须登录才能访问的接口..
案例3: 后端drf项目支持很多应用, 比如网页、小程序、app, 他们都可以访问后端某个接口.访问时需要认证,获取token信息.
      一般来说, 我们可以约定这些应用的token信息通过url传递, 但前端开发不同意..
      它非要在网页中将token放到url、小程序和app中将token放到请求头中.. 这就必须让后端的接口在认证时兼顾这三种情况..

★ 示栗中,若不用NoAuthentication认证类,那么就是支持匿名访问;加上NoAuthentication,表明必须认证登陆成功才能访问!!
  扩展思维:当然可以匿名访问,然后根据request.user是否为空在业务函数里做判定.
  
认证时,报错 重写401 不重写403  NoAuthentication杜绝匿名用户访问.
若啥也不做,drf的配置文件里就有两个默认的认证类,他们皆返回的是None!


Q:只要给视图类配置了认证,那么该视图类的 所有方法/所有视图函数 都得经过认证后才能执行!(每个请求都会执行一遍截图中的认证源码)
  若我们自己写的视图类中有get、post、put等方法,只需要对put方法进行认证,怎么办?/ 即get、post方法不需要认证就可以执行.
A: 那么在认证类中判断请求类型是否是PUT,只对其进行认证即可!!
   或者再写个视图类,单独实现put方法,加上认证,就完了!
   - 还有种方法,重写源码中的 perform_authentication方法!
     if request.method == "PUT":
         # 正常的进行认证
         request.user
     # 提示: 不需要认证,就是匿名用户呗.
     request.user = None
     request.auth = None
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

image-20231105185038025

补充一点源码阅读理解, 关于 异常处理的!!

image-20231106205725804


在开发API过程中, 有些功能需要登录才能访问, 有些无需登录. drf中的认证组件主要就是用来实现此功能!

# 快速使用 - 案例1

案例1: 项目要开发3个接口, 其中1个是无需登录就能访问的接口、其余2个是必须登录才能访问的接口.

# 实验结果

127.0.0.1:8000/login/             -->   "LoginView"
127.0.0.1:8000/user/?token=123    -->   "UserView"
127.0.0.1:8000/order/?token=123   -->   "OrderView"
  
127.0.0.1:8000/user/    -->   {"code": 1002, "error": "认证失败"}
127.0.0.1:8000/order/   -->   {"code": 1002, "error": "认证失败"}
1
2
3
4
5
6

image-20230805015046600

# 关键代码

settings.py

# --- drf配置!!!
REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,   # 设置 request.user
    "UNAUTHENTICATED_TOKEN": None,  # 设置 request.auth
}
1
2
3
4
5

urls.py 根路由

from django.urls import path
from app01 import views

urlpatterns = [
    path('login/', views.LoginView.as_view()),
    path('user/', views.UserView.as_view()),
    path('order/', views.OrderView.as_view()),
]
1
2
3
4
5
6
7
8

views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


class MyAuthentication(BaseAuthentication):
    # BaseAuthentication的源码中有两个方法,authenticate和authenticate_header
    def authenticate(self, request):
        """ ★★★
        此处我们需重写authenticate方法: To do 用户认证
        用户认证过程: 1.读取请求传递过来的token 2.校验该token的合法性
        authenticate方法的返回值有三种:
        1> 认证成功,返回元祖
           在后续认证的相关源码中,该元祖的两个值会分别赋值给request.user、request.auth
           一般request.user存储用户信息;request.auth存储Token信息
        2> 认证失败,抛出异常,返回错误信息
        3> 返回None 涉及到多个认证类 [类1,类2,类3,类4]
           会依次执行认证类的authenticate方法,若当前类的authenticate方法返回None,会逐一往后找
           直到某个类认证成功或失败,往后的认证类的authenticate方法都不再执行
           若多个认证类都返回None,会默认返回一个匿名用户! request.user的值为None,表明是匿名访问
        """
        # 提醒一下,该authenticate方法的request是drf的request!!
        # So, 看源码可知, request.query_params等同于request._request.GET
        token = request.query_params.get("token")
        # 这里简化流程,我们简单的认为只要传了token就认为登陆成功啦!
        if token:
            return "dc", token  # 表明认证成功,而且根据源码可知,一旦认证成功,后续的认证组件就不用执行了..
        # 注意此处,传递给AuthenticationFailed的是一个字符串或传递一个字典,认证失败时,返回的结果是不一样的!!
        # raise AuthenticationFailed("认证失败!")  # {"detail":"认证失败!"}
        raise AuthenticationFailed({"code": 1002, "error": "认证失败"})  # {"code": 1002, "error": "认证失败"}
        
    
    # 此处 对认证失败时响应头里的状态码进行了统一,在认证类里重写authenticate_header方法401,不重写就是403!!
    # 一般为了统一,都会加上!!
    def authenticate_header(self, request):
        """
        return 'Basic realm="API"' 
        若这样返回,在认证失败后,浏览器会接受到WWW-Authenticate:API这样的响应头
        此时,浏览器会弹出一个输入框,叫用户输入用户名和密码.. 该功能在ftp服务、或者老的路由器中可能会遇到.
        该基础认证在我们的项目开发中用不到. So,我们直接返回‘API’即可.  
        ◆ 认证失败响应头原本就是401,重写authenticate_header方法后,保持原样是401; 
          不重写该方法,401会变成403! (在APIview的源码handle_exception方法处可以得到验证.
        """
        return "API"


class LoginView(APIView):
    authentication_classes = []  # 不需要走认证类直接访问就行

    def get(self, request):
        print(request.user, request.auth)  # None None
        return Response("LoginView")


class UserView(APIView):
    authentication_classes = [MyAuthentication, ]  # 接口需携带token登陆成功才能访问 

    def get(self, request):
        print(request.user, request.auth)  # dc 123
        return Response("UserView")


class OrderView(APIView):
    authentication_classes = [MyAuthentication, ]  # 接口需携带token登陆成功才能访问 

    def get(self, request):
        print(request.user, request.auth)  # dc 123
        return Response("OrderView")       # dc 123
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# 常见示例 - 案例2

案例2: 项目要开发100个接口, 其中1个是无需登录就能访问的接口、其余99个是必须登录才能访问的接口..
此时就需要用到drf的全局配置!!!

★特别注意!! 当我们使用drf的全局配置时, 认证组件的类不能放在视图view.py中, 会因为导入APIView导致循环引用!!
在drf中, 优先会去全局中读取, 再去视图类中读取! 也就是说 若视图类中有, 会以视图类中的为准.. 没有就用全局的.

简单阐述下,此处的循环引用
配置文件中, "DEFAULT_AUTHENTICATION_CLASSES": ["app01.views.MyAuthentication", ],
加载配置文件时候,会通过"app01.views.MyAuthentication"加载views.py文件
views.py文件中 有代码 from rest_framework.views import APIView 会去加载APIView
APIview中有这样一行代码(如下),它又会去配置文件中加载,"app01.views.MyAuthentication".. 然而,该配置还未加载完毕.
"""
class APIView(View):
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
"""
So,造成了循环引用的问题!!

【解决办法】: 将认证组件的类放到单独的文件中即可!!此处是放到了ext文件夹的auth.py文件中.
  
  
api_settings,command+B跳转过去,你会看到源码中有这样几行注释:
This module provides the `api_setting` object, that is used to access
REST framework settings, checking for user settings first, then falling
back to the defaults.
意思就是,先会读取Django配置文件中的,没有的话会再读取drf配置文件中的!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

image-20230728231743272


# oop的继承

主要观察 self是哪个类的实例化对象, 优先从该类中找!!

class Base:
    xxx = "hello"
    yyy = "world"

    def f1(self):
        self.f2()        # foo.f2
        print(self.xxx)  # python
        print(self.yyy)  # world

    def f2(self):
        print("base.f2")


class Foo(Base):
    xxx = "python"

    def f2(self):
        print("foo.f2")


obj = Foo()
obj.f1()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

So, 源码中, 全局和局部都配置后, 如何查找的, 就是基于上述面向对象继承相关的知识!! 源码核心的思想如下:

class APIView:
    authentication_classes = "读取配置文件中的列表"

    def dispatch(self):
        print(self.authentication_classes)  # 自己类中有就用自己的,没有就用父类里的!!


class UserView(APIView):
    authentication_classes = []


obj = UserView()
obj.dispatch()
1
2
3
4
5
6
7
8
9
10
11
12
13

# 源码流程

小技巧: 将源码粘贴出来, 保留核心代码, 放到一处, 便于观察彼此之间的调用关系!!
再次强化记忆, self.xxx 优先自己类中找, 没有就去父类中找..

★ 回顾下,在前面详细进行了CBV的源码分析,简单来说: (确实如此,这三步是精髓)
1> 请求一来,找到自己写的UserView类
2> 实例化 obj = UserView()
3> 调用APIView里的dispatch方法 obj.dispatch()

image-20210822092707803

"""★ 大体流程简单说:
1. 在dispatch方法中,进行了请求的封装,封装的过程中进行了认证组件的加载
   <其本质就是实例化每个认证类的对象并封装到drf的request中>
   具体如何封装,要看drf的Request类的构造方法!
2. 进行认证,将我们自己写的认证类的authenticate方法返回的元祖赋值给了drf的request的 user和auth!!
3. 只要认证过程不抛出异常, 就可以/才能 执行视图函数;
   三大认证或者视图函数 他们抛出的异常都能在dispatch中顺利捕获到!
"""


class UserView(APIView):
    def get(self, request):
        # self.dispatch 这样写是个小技巧,便于在源码中直接跳转定位 为何可以呢?
        # 因为在源码中,self = cls() 对视图类直接实例化了.. self没有dispatch,就去其父类APIView中找!
        print(request.user, request.auth)
        return Response("UserView")
      
      
class APIView(View):
    # ★★★ 这里有个小细节,若要全局配置,DEFAULT_AUTHENTICATION_CLASSES就是key值
    #     再看这里,authentication_classes后面有s,表明是复数,value值就是个列表,["..",".."]
    #     若后面没有s,那么value值就是 ".."
    #     那全局配置时,字典名哪里找,点击api_settings,在reload_api_settings函数源码中,可看到
    #     指定了 setting == 'REST_FRAMEWORK'
    #     (这些小技巧,使得不用翻笔记,直接查源码即可,菜鸟进阶必备技巧!)
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES  # 读取配置文件中的那个认证列表
    
    # ★★★★★★ 注意哦! 按照源码流程分析,到此处调用dispatch的self是视图类的实例化对象,不是APIView的实例化对象.
    def dispatch(self, request, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        
        #【第一步】下面两行代码进行了请求的封装, "!<意味着往下的request都是drf中的request>!"
        # 请求的封装里封装了 --> django的request对象 + authenticators认证组件 + ..
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        
        self.headers = self.default_response_headers

        try:
            #【第二步】该行代码包含了认证
            self.initial(request, *args, **kwargs)

            if request.method.lower() in self.http_method_names:
                # 反射获取get/post/put/delete等方法
                handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            #【第三步】执行视图函数
            response = handler(request, *args, **kwargs)

        except Exception as exc:  # 捕获异常 
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response  # 页面上显示 视图函数的返回/异常错误信息
      
      
    def initialize_request(self, request, *args, **kwargs):
        parser_context = self.get_parser_context(request)

        # 点击Request,command+b进行跳转. 看下Request类的构造方法!
        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),  # 将认证组件中的所有对象封装到了drf的request中!!
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
      

    def get_authenticators(self):
        # self.authentication_classes 优先在自己类中找,没有,就在父类APIView中找
        # 父类APIView中是有authentication_classes这个类变量的,它的值是我们写在配置文件里的全局配置
        # (虽说从配置文件中读取到的是字符串,但不用担心,它会自动通过反射找到相应的类)
        # 循环出来的是类,auth()进行了类的实例化操作!!
        return [auth() for auth in self.authentication_classes]  # 得到了多个认证类的实例化对象!!
      
      
    def initial(self, request, *args, **kwargs):
        self.perform_authentication(request)  # 进行认证
        
        self.check_permissions(request)
        self.check_throttles(request)
        
        
    def perform_authentication(self, request):
        request.user
        
        
    # - 对认证失败时,响应头的状态码403和401的探究!!
    def handle_exception(self, exc):  # exc是抛出的异常
        # AuthenticationFailed类型的错误是我们在认证类的authenticate函数中认证不通过抛出的异常!!
        if isinstance(exc, (exceptions.NotAuthenticated,
                            exceptions.AuthenticationFailed)):
            # WWW-Authenticate header for 401 responses, else coerce to 403
            auth_header = self.get_authenticate_header(self.request)

            if auth_header:
                exc.auth_header = auth_header
            else:
                exc.status_code = status.HTTP_403_FORBIDDEN
        ... ... ...
        return response
      
      
    def get_authenticate_header(self, request):
        authenticators = self.get_authenticators()  # 获取多个认证类的实例化对象,在第一步中对request的封装也用到了.
        if authenticators:
            # 执行自己写的认证类里的authenticate_header方法!!若无该方法,会执行父类BaseAuthentication里的
            # 只不过BaseAuthentication里的authenticate_header方法里写了个pass,代表啥也没写!!
            return authenticators[0].authenticate_header(request)
      
      
class Request:
    def __init__(self, request, authenticators=None):
        self._request = request
        self.authenticators = authenticators or ()
        
    @property
    def user(self):
        # 最开始,Request类的构造方法里是没有 _user 这个成员的.
        # Ps:格外话,思考并验证了一个问题,若构造方法中写了self.user = None又通过@property装饰了user方法,是会冲突报错的!!
        #    报错,AttributeError: can't set attribute  
        # (后来人的补充标注:上述这个格外话,不严谨.看看就行,不要纠结!)
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
      
     
    @user.setter
    def user(self, value):
        self._user = value
        self._request.user = value
        
    
    # 细品,认证成功,在视图类的函数里调用request.user 直接返回的就是self._auth 
    # 一开始时,肯定会执行self._authenticate()
    @property
    def auth(self):
        if not hasattr(self, '_auth'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._auth

    
    @auth.setter
    def auth(self, value):
        self._auth = value
        self._request.auth = value
      
      
    def _authenticate(self):
        # 读取每个认证组件对象,执行authenticate方法!!
        # 注意多个认证类时的执行流程!!
        """
        会依次执行认证类的authenticate方法,若当前类的authenticate方法返回None,会逐一往后找
        直到某个类认证成功或失败,往后的认证类的authenticate方法都不再执行
        若多个认证类都返回None,会默认返回一个匿名用户! request.user的值为None,表明是匿名访问
        ★ 注意,若都返回None,通过对dispatch函数的分析,视图是会执行的!!只不过,self.user、self.auth皆为None.
        """
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)  # !!此处的self是drf的request对象!!
            # -- 若认证失败,authenticate函数里会抛出<认证失败AuthenticationFailed>的异常!
            #    注意,AuthenticationFailed继承了APIException!
            except exceptions.APIException:
                self._not_authenticated()  # ▲ 认证失败执行该方法
                raise  # !!!然后将异常往上抛..
                """怎么个抛法?
                Request类的user方法调用的_authenticate方法,user方法中没有捕获,再往上看
                APIView类的perform_authentication调用user方法,没有捕获,再往上抛
                APIView类的initial调用perform_authentication方法,没有捕获,再往上抛
                APIView类的dispatch方法调用initial方法,在该位置有异常处理!!
                -- 捕获了异常,执行了APIView类里的handle_exception方法!!
                """

            # 认证通过后,即user_auth_tuple的bool值为True,走这里
            if user_auth_tuple is not None:
                self._authenticator = authenticator
                # 在我们自己写的视图类的函数中,可通过request.user,request.auth取到
                # ★!! 注意:赋值就会调用@user.setter以及@auth.setter装饰的方法!! 这样self._auth中就有值啦.
                self.user, self.auth = user_auth_tuple  # 将元祖进行了 拆包/解压赋值
                return  # 后续的认证类就不用管啦! 这里直接跳出了for循环!要引起注意哦!

        self._not_authenticated()  # ▲ 所有认证类的authenticate方法全都返回None,也会执行该方法.
        
    # 这玩意儿跟匿名用户相关.
    def _not_authenticated(self):
        self._authenticator = None

        # 去配置文件中读取,判断为真,就加括号执行 所以在settings.py中的drf配置
        """
        REST_FRAMEWORK = {
            "UNAUTHENTICATED_USER": None,
            "UNAUTHENTICATED_TOKEN": None,
        }
        UNAUTHENTICATED_USER、UNAUTHENTICATED_TOKEN 其值是可以写成匿名函数的!
        """
        if api_settings.UNAUTHENTICATED_USER:  
            self.user = api_settings.UNAUTHENTICATED_USER() 
        else:
            self.user = None

        if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()
        else:
            self.auth = None
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210

# 常见示例 - 案例3

案例3: 后端drf项目支持很多应用, 比如网页、小程序、app, 他们都可以访问后端某个接口..
访问时需要认证, 获取token信息, 一般来说, 我们可以约定这些应用的token信息通过url传递, 但前端开发不同意..
它非要在网页中将token放到url、小程序和app中将token放到请求头中.. 这就必须让后端的接口在认证时兼顾这三种情况..

这就得对 源码中 多个认证类 执行流程的进行分析.. 然后再编写代码实现.

下方的代码 是用"局部"配置的方式来实现的.. 可以自行改成"全局"的方式.

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.views import APIView
from rest_framework.response import Response


# 从URL获取token信息
class URLAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if token:
            return "dc", token
        return


# 从请求头中获取token信息
class HeaderAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get("HTTP_AUTHORIZATION")  # HTTP_ 的前缀是Django自己加的
        if token:
            return "dc", token
        return


# 前面的认证都返回None,那么最后这抛出异常
class NoAuthentication(BaseAuthentication):
    def authenticate(self, request):
        raise AuthenticationFailed({"code": 1002, "error": "认证失败"})
        
        
class OrderView(APIView):
    # URLAuthentication, HeaderAuthentication 都没认证成功,就执行NoAuthentication主动抛出异常!!
    # ★ 若不用NoAuthentication认证类,那么就是支持匿名访问;加上NoAuthentication,表明必须认证登陆成功才能访问!!
    authentication_classes = [URLAuthentication, HeaderAuthentication, NoAuthentication]

    def get(self, request):
        print(request.user, request.auth)  # 若支持匿名访问的话,值就是None、None
        return Response("LoginView")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 综合案例 - 用户登陆+认证

用户登陆成功后, 后端返回前端一个token, 前端将其进行存储(eg: 网页可存在cookie以及storage中)
浏览器携带该token访问后端的接口.

# 关键代码

login登陆接口不需要认证; user用户接口需要认证!!

settings.py

# --- drf配置!!!
REST_FRAMEWORK = {
    "UNAUTHENTICATED_USER": None,  # 设置 request.user
    "UNAUTHENTICATED_TOKEN": None,  # 设置 request.auth
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "ext.auth.QueryParamsAuthentication",
        "ext.auth.HeaderAuthentication",
        "ext.auth.NoAuthentication",
    ],
}
1
2
3
4
5
6
7
8
9
10

models.py

"""
数据库迁移后,在数据库中手动添加数据
xiaoming 123123
xiaohong 123456
"""

from django.db import models


class UserInfo(models.Model):
    """用户表"""
    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)
    # 为了简便,此处临时将token放在数据库中;drf项目中token的存放,一般会借助jwt.
    token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

urls.py 根路由

from django.urls import path
from app01 import views

urlpatterns = [
    path('login/', views.LoginView.as_view()),
    path('user/', views.UserView.as_view()),
]
1
2
3
4
5
6
7

app01/views.py

import uuid

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

from app01 import models


class LoginView(APIView):
    authentication_classes = []

    def post(self, request):
        # 1.接收用户POST请求提交的用户名和密码
        # request._request.body --:> b'{"username":"xiaoming","password":"123123"}'
        # request.data --:> {'username': 'xiaoming', 'password': '123123'}
        user = request.data.get("username")
        pwd = request.data.get("password")
        # 2.数据库校验
        # user_obj = models.UserInfo.objects.filter(**request.data).first()
        user_obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
        if not user_obj:
            return Response({"status": False, "msg": "用户名或密码错误"})
        # 注意:每次登陆成功,数据库中该用户的token都会更改
        token = str(uuid.uuid4())
        user_obj.token = token
        user_obj.save()
        return Response({"status": True, "data": token})


class UserView(APIView):
    def get(self, request):
        print(request.user, request.auth)
        return Response("UserView-get")

    def post(self, request):
        print(request.user, request.auth)
        return Response("UserView-post")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

ext/auth.py

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01 import models


class QueryParamsAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:  # 加该判断是因为,在数据库中手动创建的用户的token信息一开始是空的
            return
        user_object = models.UserInfo.objects.filter(token=token).first()
        if user_object:
            return user_object, token
        return

    def authenticate_header(self, request):
        return "API"


class HeaderAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META.get("HTTP_AUTHORIZATION")  # HTTP_ 的前缀是Django自己加的
        if not token:
            return
        user_object = models.UserInfo.objects.filter(token=token).first()
        if user_object:
            return user_object, token
        return

    def authenticate_header(self, request):
        return "API"


class NoAuthentication(BaseAuthentication):
    def authenticate(self, request):
        raise AuthenticationFailed({"status": False, "error": "认证失败"})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 进行验证

用Postman进行验证!! -- 使得我们无需关注前端的编写.

-- 登陆
   POST请求  http://127.0.0.1:8000/login/  发送json格式数据{"username":"xiaoming","password":"111111"}
       请求成功,后端返回
           {"status": true,"data": "63f453fd-5c52-473d-aa6b-a276208ecd34"}
       请求失败,后端返回
           {"status": false,"msg": "用户名或密码错误"}
-- 访问接口
   GET请求  http://127.0.0.1:8000/user/?token=63f453fd-5c52-473d-aa6b-a276208ecd34
       请求成功,后端返回
           "UserView-post"
   POST请求 http://127.0.0.1:8000/user/  请求头中添加,AUTHORIZATION=63f453fd-5c52-473d-aa6b-a276208ecd34
       请求成功,后端返回
           "UserView-post"
   请求失败,无论是GET还是POST请求,后端都返回
       {"status": "False","error": "认证失败"}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 模拟

用python代码模拟认证源码执行过程 (包含了drf的CBV过程)

auth.py

import random


class BaseAuthentication:
    def authenticate(self, request):
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        pass


class QueryParamsAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # token = request.query_params.get("token")
        token = random.choice([0, 1])
        if token:
            return "dc-query", token

    def authenticate_header(self, request):
        return "API"


class HeaderAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # token = request.META.get("HTTP_AUTHORIZATION")
        token = random.choice([0, 1])
        if token:
            return "dc-header", token

    def authenticate_header(self, request):
        return "API"


class NoAuthentication(BaseAuthentication):
    def authenticate(self, request):
        raise Exception("报错-前面的都返回None,认证失败")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

run.py

from auth import QueryParamsAuthentication, HeaderAuthentication, NoAuthentication


class View:
    @classmethod
    def as_view(cls):
        def view(request, *args, **kwargs):
            self = cls()
            return self.dispatch(request, *args, **kwargs)

        return view

    def dispatch(self, request, *args, **kwargs):
        pass


class APIView(View):
    authentication_classes = [QueryParamsAuthentication, HeaderAuthentication]

    @classmethod
    def as_view(cls):
        # view = super(APIView, cls).as_view()
        view = super().as_view()
        return view

    def dispatch(self, request, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)  # 二次封装request
        try:
            self.initial(request, *args, **kwargs)
            handler = getattr(self, request.method.lower())
            return handler(request, *args, **kwargs)
        except Exception as e:
            print(e)

    def initialize_request(self, request, *args, **kwargs):
        return Request(
            request,
            authenticators=self.get_authenticators(),
        )

    def get_authenticators(self):
        return [auth() for auth in self.authentication_classes]

    def initial(self, request, *args, **kwargs):
        self.perform_authentication(request)  # 进行认证

    def perform_authentication(self, request):
        request.user


class Request:
    def __init__(self, request, authenticators=None):
        self._request = request
        self.authenticators = authenticators or ()

    def __getattr__(self, attr):
        try:
            return getattr(self._request, attr)
        except AttributeError:
            return self.__getattribute__(attr)

    @property
    def user(self):
        if not hasattr(self, '_user'):
            self._authenticate()
        return self._user

    @user.setter
    def user(self, value):
        self._user = value
        self._request.user = value

    def _authenticate(self):
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)
            except Exception:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

    def _not_authenticated(self):
        self.user = None
        self.auth = None


if __name__ == '__main__':
    import random


    class DjangoRequest:
        def __init__(self):
            self.method = random.choice(['GET', 'POST'])  # 模拟发送的请求类型


    class InfoView(APIView):
        # 有NoAuthentication,则不让其匿名访问,即不会执行视图函数
        authentication_classes = [QueryParamsAuthentication, HeaderAuthentication, NoAuthentication]

        def get(self, request, xx):
            print(self.args, self.kwargs)  # (111,) {}
            print(request.user, request.auth)
            return "InfoView-GET"

        def post(self, request, xx):
            print(self.args, self.kwargs)  # (111,) {}
            print(request.user, request.auth)
            return "InfoView-POST"


    request = DjangoRequest()
    print(InfoView.as_view()(request, 111))  # 111是模拟路由参数传递!
  

"""打印结果可能是下方5种中的任意一种,因为程序里是随机的.
(111,) {}
dc-query 1
InfoView-POST

(111,) {}
dc-query 1
InfoView-GET

(111,) {}
dc-header 1
InfoView-POST

(111,) {}
dc-header 1
InfoView-GET

报错-前面的都返回None,认证失败
None
"""
"""
Ps:若将InfoView类中的authentication_classes变量删除,相当于没有NoAuthentication
   那么运行结果的request.user、request.auth 的值还可能是 None None 表明是匿名访问啦.
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

drfCBV
权限

← drfCBV 权限→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式