DRF:认证(单视图或全局设置认证方案和源码分析、设置多个认证方案、如何设置不允许匿名访问)

news2025/1/18 14:37:19

概念:request.user、request.auth、认证方案authentication_classes

官网原文:

验证始终在视图的最开始进行,在执行权限和限制检查之前以及允许任何其他代码继续执行之前。

request.user 属性通常被设置为contrib.auth 包中 User 类的一个实例。

request.auth 属性用于任何其他身份验证信息,例如,它可以用于表示请求签名的身份验证令牌。

注意: 不要忘了认证本身不会允许或拒绝传入的请求,它只是简单识别请求携带的凭证。所以即使认证不通过,也会执行视图函数,只不过request.user和request.auth为None。这种叫允许匿名访问。如果想要设置不允许,请见后面的设置方案 > 不允许匿名访问

认证方案authentication_classes总是被定义为一个类的列表。DRF将尝试使用列表中的每个类进行身份验证,并使用成功完成验证的第一个类的返回值设置 request.user 和request.auth。

如果没有类进行验证,request.user 将被设置成 django.contrib.auth.models.AnonymousUser的实例(也就是匿名用户),request.auth 将被设置成None。

未认证请求的request.user 和 request.auth 的值可以使用 UNAUTHENTICATED_USER和UNAUTHENTICATED_TOKEN 设置进行修改。

设置认证方案

单视图设置优先度更高,会覆盖全局设置。
源码分析请见本篇博客最后的源码分析 > 全局和局部设置认证方案的继承源码
认证方案不能写在views中,因为会出现循环引用的问题。

全局设置

可以使用 DEFAULT_AUTHENTICATION_CLASSES 设置全局的默认身份验证方案。比如:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'app01.tools.auth.MyAuthentication'
        # 自定义认证类
    )
}

单视图设置

你还可以使用基于APIView类视图的方式,在每个view或每个viewset基础上设置身份验证方案。
譬如如下:
toosl/auth.py

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


class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        # drf获得路径上的参数都是用query_params.get()方法获得
        if token:
            return ("shanshan", token)
            # 实际上这里返回的是一个元组,元组中的第一个元素为request.user,第二个元素为request.auth
        else:
            raise AuthenticationFailed({"code": 2000, "error": "token is not exist"})

views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from app01.tools.auth import MyAuthentication


class LoginView(APIView):
    authentication_classes = []

    def get(self, request):
        return Response("LoginView")


class UserView(APIView):
    authentication_classes = [MyAuthentication, ]

    def get(self, request):
        return Response("UserView")

urls.py

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


urlpatterns = [
    path(r'admin/', admin.site.urls),
    path(r'user/', views.UserView.as_view()),
    path(r'login/', views.LoginView.as_view())
]

或者,如果你使用基于函数的视图,那就使用@authentication_classes装饰器。

@api_view(['GET'])
@authentication_classes((SessionAuthentication, BasicAuthentication))
def example_view(request, format=None):
    content = {
        'user': unicode(request.user),  # `django.contrib.auth.User` 实例。
        'auth': unicode(request.auth),  # None
    }
    return Response(content)

多个认证方案

可能存在以下应用场景就是,token可能从url中获取,可能从请求头中获取,可能从请求体中获取。那么我们就不可能只写一个认证方案了,而且也不可以直接在认证方案中抛出错误,因为即使这个认证没有通过,它可能在下个认证中通过了。
此时代码如下:
tools/auth.py:

from rest_framework.authentication import BaseAuthentication


class UrlAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        # drf获得路径上的参数都是用query_params.get()方法获得
        if token:
            return ("shanshan", token)
            # 实际上这里返回的是一个元组,元组中的第一个元素为request.user,第二个元素为request.auth
        else:
            return None


class HeaderAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # drf获得请求头上的参数都是用META.get()方法获得
        # 注意这里虽然获取用的是HTTP_AUTHORIZATION,但是实际用apifox等测试,传给apifox的参数是Authorization
        token = request.META.get("HTTP_AUTHORIZATION")
        if token:
            return ("shanshan", token)
        else:
            return None


class BodyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # drf获得请求体上的参数都是用data.get()方法获得
        token = request.data.get("token")
        if token:
            return ("shanshan", token)
        else:
            return None

views.py

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

from app01.tools.auth import UrlAuthentication, HeaderAuthentication, BodyAuthentication, NoAnonymousAuthentication


class LoginView(APIView):
    authentication_classes = []

    def get(self, request):
        return Response("LoginView")


class UserView(APIView):
    authentication_classes = [UrlAuthentication, HeaderAuthentication, BodyAuthentication, NoAnonymousAuthentication]

    def get(self, request):
        return Response("UserView")

    def post(self, request):
        re

不允许匿名访问

需要再多加一个认证方案,如下:

from rest_framework.exceptions import AuthenticationFailed

class NoAnonymousAuthentication(BaseAuthentication):
    def authenticate(self, request):
        raise AuthenticationFailed({"code": 2000, "error": "token is not exist and reject anonymous user"})

能走到这个认证方案,说明前面的方案都没有通过,返回了None,所以直接在这个方案里面抛出异常即可。

源码分析

全局和局部设置认证方案的继承源码

首先来看一段继承的代码

class Base(object):
    a = 123

    def f1(self):
        self.f2()
        print(self.a)

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


class Son(Base):
    a = 456

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


obj = Son()
obj.f1()

执行这段代码,实际打印结果是

son.f2
456

调用链如下:
obj.f1() -> Base.f1() -> self.f2()
那么关键来了,这里的self.f2()究竟调用的是Base的f2,还是Son的f2。
答案就是Son的f2,因为obj是Son的实例,所以self也是Son。
同样的,打印的self.a,也是打印的Son中的a

那么将上述的代码的f2去掉,只看类变量a,再修改一下,其实就是DRF中全局和局部设置认证方案的继承源码

from django.conf import settings
from app01.tools.auth import MyAuthentication


class APIView(object):
    authentication_classes = settings.AUTHENTICATION_CLASSES

    def dispatch(self):
        print(self.authentication_classes)


class UserView(APIView):
    authentication_classes = [MyAuthentication, ]


obj = UserView()
obj.dispatch()

这也就是为什么局部优先度更高的原因。

DRF完整认证源码分析

rest_framework/views.py

    def dispatch(self, request, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        # 以上为处理参数
		
        request = self.initialize_request(request, *args, **kwargs)
        # 以上为封装请求,现在的request=原生request+认证组件authenticators(认证方案列表的实例化列表)
        
        self.request = request
        
		try:
			self.initial(request, *args, **kwargs)
			handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
			response = handler(request, *args, **kwargs)
        	# 以上为正式执行视图函数的语句
    	except Exception as exc:
            response = self.handle_exception(exc)

    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

rest_framework/request.py

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

    @property
    def user(self):
    # 整个函数主要是为了初始化_user属性或者读取_user属性
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user

    def _authenticate(self):
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)
                # 由自定义或者默认的认证方案的authenticate方法,得到元组赋给user和auth
            except exceptions.APIException:
                self._not_authenticated()
                # 认证不通过
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return
		# 说明前面的认证方案都没有通过,都返回了None
        self._not_authenticated()

    def _not_authenticated(self):
        self._authenticator = None

        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

    @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

具体调用链就是
在这里插入图片描述

认证码

当未经身份验证的请求被拒绝时,有下面两种不同的错误代码可使用。

  • HTTP 401 未认证,必须始终包括一个WWW-Authenticate头
  • HTTP 403 无权限,不包括WWW-Authenticate

具体使用哪种响应取决于认证方案。虽然可以使用多种认证方案,但是仅可以使用一种方案来确定响应的类型。

注意,当一个请求通过了验证但是被拒绝执行请求的权限时,不管认证方案是什么,都要使用 403 Permission Denied 响应。

认证码不一致的问题

我们再次回到上方的调用图,可以看到只有initial()后面的代码才有try catch

    def handle_exception(self, exc):
        """
        Handle any exception that occurs, by returning an appropriate response,
        or re-raising the error.
        """
        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

        exception_handler = self.get_exception_handler()

        context = self.get_exception_handler_context()
        response = exception_handler(exc, context)

        if response is None:
            self.raise_uncaught_exception(exc)

        response.exception = True
        return response

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1572190.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数学知识--(欧拉函数,快速幂,扩展欧几里得算法)

本文用于记录个人算法竞赛学习,仅供参考 目录 一.欧拉函数 二.欧拉函数模板 三.用筛法求每个数的欧拉函数 四.快速幂 五.扩展欧几里得算法 六.用扩展欧几里得算法求线性同余方程 一.欧拉函数 即有一个数n, n通过质因数分解得到 通过欧拉函数有 证明&…

Java 程式 main 方法传参数

Java 程式运行时如果需要传递参数时,常用的方法有两种: 使用 Program Arguments 来传递值使用 VM Arguments 来传递值 1、使用 Program Arguments 来传递值 使用 Program Arguments 来传递值时,main 方法的写法如下: public st…

Linux 常用命令(持续更新中...)

1. ls 查看文件列表命令 语法: ls [-a -l -h] [Linux路径] -a -l -h 是可选的选项 (-h需配合-l命令一起使用)Linux路径是此命令可选的参数 ls #查看当前目录所有非隐藏文件(平铺方式显示) ls -a #查看当前目录下所有文件 …

Web 后台项目,权限如何定义、设置、使用:菜单权限、按钮权限 ts element-ui-Plus

Web 后台项目,权限如何定义、设置、使用:菜单权限、按钮权限 ts element-ui-Plus 做一个后台管理项目,里面需要用到权限管理。这里说一下权限定义的大概,代码不多,主要讲原理和如何实现它。 一、权限管理的原理 权限…

Polardb MySQL 产品架构及特性

一、产品概述; 1、产品族 参考:https://edu.aliyun.com/course/3121700/lesson/341900000?spma2cwt.28120015.3121700.6.166d71c1wwp2px 2、polardb mysql架构优势 1)大容量高弹性:最大支持存储100T,最高超1000核CPU&#xff0…

55、美国德克萨斯大学奥斯汀分校、钱德拉家族电气与计算机工程系:通过迁移学习解决BCI个体差异性[不得不说,看技术还得是老美]

2024年2月5日跨被试最新文章: 德州州立大学奥斯汀分校研究团队最近的一项研究成果,通过非侵入式的脑机接口,可以让被试不需要任何校准就可以使用脑机接口设备,这意味着脑机接口具备了大规模被使用的潜力。 一般来说,…

哈希-字母异位词分组

字母异位词&#xff0c;词频一样&#xff0c;但是顺序不一样&#xff0c;可以进行排序&#xff0c;获取一个key&#xff0c;放在map中即可。 class Solution {public List<List<String>> groupAnagrams(String[] strs) {Map<String, List<String>> ma…

彩虹聚合DNS管理系统v1.0全新发布

聚合DNS管理系统&#xff08;https://github.com/netcccyun/dnsmgr&#xff09;可以实现在一个网站内管理多个平台的域名解析&#xff0c;目前已支持的域名平台有&#xff1a;阿里云、腾讯云、华为云、西部数码、CloudFlare。本系统支持多用户&#xff0c;每个用户可分配不同的…

STM32CubeIDE基础学习-舵机控制实验

STM32CubeIDE基础学习-舵机控制实验 文章目录 STM32CubeIDE基础学习-舵机控制实验前言第1章 硬件介绍第2章 工程配置2.1 基础工程配置部分2.2 生成工程代码部分 第3章 代码编写第4章 实验现象总结 前言 SG90、MG996舵机在机器人领域用得非常多&#xff0c;因为舵机有内置控制电…

利用nginx-http-flv-module实现三种直播

目录 一、说明 二、目标 三、实现 四、直播地址 一、说明 此文在《流媒体服务器的搭建(支持hls)》《搭建nginx-http-flv-module直播系统》之后编写,很多详细内容需要参考它。 流媒体服务器的搭建(支持hls)

【面经】interrupt()、interrupted()和isInterrupted()的区别与使用

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;面经 ⛺️稳中求进&#xff0c;晒太阳 interrupt方法 如果打断线程正在sleep&#xff0c;wait&#xff0c;join会导致被打断的线程抛出InterruptedException&#xff0c;并清除打断标记。如…

商标“五分法”,如何起名显著性更强通过率更高!

1976年在Abercrombie一案美国判例中提出的商标五分法&#xff0c; 基本上在全球范围内得到认可和共识&#xff0c;普推知产老杨平常检索时&#xff0c;我国一些专家相关的论文及专著和判例中也会经常涉及到。 商标五分法主要是把商标分成个五种类型&#xff0c; 通用的&#xf…

Linux:数据链路层

文章目录 路由表数据链路层分片mac帧报头ARP协议ARP的周边话题 路由表 当主机a想要发送消息到主机b&#xff0c;这一整个过程中&#xff0c;数据报文在进行传输的过程实际上是一跳一跳的过去的&#xff0c;而报文可能会经过公网进行传递&#xff0c;本质上这些网络都是靠对应的…

【Arthas案例】某应用依赖两个GAV-classifier不同的snakeyaml.jar,引起NoSuchMethodError

多个不同的GAV-classifier依赖冲突&#xff0c;引起NoSuchMethodError Maven依赖的三坐标体系GAV(G-groupId&#xff0c;A-artifactId&#xff0c;V-version) classifier通常用于区分从同一POM构建的具有不同内容的构件物&#xff08;artifact&#xff09;。它是可选的&#xf…

美食分享|基于Springboot和vue的地方美食分享网站系统设计与实现(源码+数据库+文档)

地方美食分享网站系统 目录 基于Springboot和vue的地方美食分享网站系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1、前台&#xff1a; 2、后台 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介…

四信AI智能视频边缘分析盒+传感云平台,开启食品安全智慧监管新模式

方案背景 民以食为天&#xff0c;食品是人类生存必备的物质之一&#xff0c;食品生产安全关乎每个人的生命健康与社会可持续发展。在食品生产过程中&#xff0c;如何实现安全、健康生产是监管机构首要考虑因素&#xff0c;也是当今社会必须共同关注与努力的方向。 监管机构必…

数据库性能优化入门:数据库分片初探

数据库分片是一种用于提升数据库性能的架构模式&#xff0c;选择正确的分片策略和实施方式对于提高数据库性能和应对大规模数据挑战至关重要。 本文介绍了数据库分片的定义、原理和实施方法。文章解释了数据库分片是如何通过将数据切分、分散存储在多个服务器上来提升性能&…

【Kotlin】委托模式

1 委托模式简介 委托模式的类图结构如下。 对应的 Kotlin 代码如下。 fun main() {var baseImpl BaseImpl()var baseWrapper BaseWrapper(baseImpl)baseWrapper.myFun1() // 打印: BaseImpl, myFun1baseWrapper.myFun2() // 打印: BaseImpl, myFun2 }interface Base {fun my…

C语言第四十弹---预处理(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 预处理 1、#和## 1.1 #运算符 1.2、##运算符 2、命名约定 3、#undef 4、命令行定义 5、条件编译 6、头文件的包含 6.1、头文件被包含的方式 6.1.1、本地…

docker从入门到熟悉

一、什么是docker&#xff1f; Docker是一个用于开发&#xff0c;交付和运行应用程序的开放平台。Docker使您能够将应用程序与基础架构分开&#xff0c;从而可以快速交付软件。借助Docker&#xff0c;您可以以与管理应用程序相同的方式来管理基础架构。通过利用Docker的快速交付…