权限
1> 先认证, 认证成功,返回元祖/认证失败raise-报错/认证失败,返回None-匿名用户访问.
注意:认证失败报错走不到权限校验! 单独只看认证,匿名访问是可以往后走到视图函数的.
2> 然后权限校验, 权限校验默认是满足多个条件且的关系, 但可根据应用场景进行扩展自定义 满足多个条件中的某个条件即可.
(Ps:认证可以从url/请求头等中拿到Token信息,进行校验;权限也就是条件判断,比如角色,该接口只有员工或经理才能访问"或关系")
认证组件=[认证类,认证类,认证类]
循环认证组件,依次执行每个认证类里的authenticate方法
- 一旦某个认证类认证成功或者认证过程抛出异常导致认证失败,不会再管后续的认证类;
- 只有返回None,才会继续往后执行下一个认证类里的authenticate方法;
简而言之,这么些个认证类,只要有一个认证成功或失败,后续的认证类就不用管了
<None None None raise 只要有一个成功即可> -- 此时,就类似于 or或的关系!!
权限组件=[权限类,权限类,权限类]
循环权限组件,依次执行每个权限类里的has_permission方法 会执行所有的权限类
- 默认情况下/源码的默认逻辑,得保证所有权限类的has_permission方法都返回True,才表明权限验证通过
(权限组件默认得所有都成功`也就是项目中的某个请求的访问得同时满足A条件、B条件、C条件` and且;)
- 研究源码后,可以进行扩展+自定义,将权限的验证逻辑变成 or或的关系!!灵活应用. A、B、C条件,只需满足A条件 或 B条件 或C条件.
★★★
- 认证为匿名用户,权限通过,是能走到视图函数的;
认证为匿名用户,权限不通过,走不到视图函数,会报401错误;
认证通过不是匿名用户,权限不通过,走不到视图函数,会报403错误;
- has_permission(self, request, view) 里有个参数view
- 若啥也不做,drf的配置文件里就有两个默认的认证类,他们皆返回的是None!
若啥也不做,drf的配置文件里还有个默认的权限类,它返回的是True!直接通过权限校验.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
导言
在开发API过程中, 有些接口的访问必须同时满足条件A、B、C; 有些接口的访问只需满足其中任意一个条件..
要实现该需求, 就得用到drf的权限..
前者是且关系, drf的权限默认支持; 后者是或关系,可扩展通过自定义来实现!!
# 快速使用(且关系)
此处, 有三个权限类, 默认情况下, 必须所有权限类的has_permission方法的执行都返回True,权限校验才会通过!!
可自定义权限校验不通过的错误提示信息;
权限和认证一样,可以进行全局配置和局部配置!! 且它们的应用规则是一样的, 局部优先!
注意:
1> 认证和权限一起使用,会先进行认证,认证通过后,才进行权限校验..
2> 像登陆接口是不需要进行认证和权限的,登陆的视图类中需写上, authentication_classes = []
permission_classes = []
# 实验结果
访问 `http://127.0.0.1:8000/order/?token=dd5df47b-87ba-435a-8430-97bdada16c71` 接口,
会返回信息 {"status": "1003","msg": "无权访问2"}
Ps:默认的权限校验失败错误提示信息是这样的.
{"detail": "You do not have permission to perform this action."}
控制台只打印
permission1
permission2
!!证明,认证失败的错误信息是MyPermission2类的,不会再管后续的权限类MyPermission3.
2
3
4
5
6
7
8
9
# 关键代码
ext/per.py
from rest_framework.permissions import BasePermission
class MyPermission1(BasePermission):
message = {"status": 1003, "msg": "无权访问1"} # 自定义权限的错误提示信息!!
def has_permission(self, request, view): # view是视图类的实例化对象
print("permission1")
return True
class MyPermission2(BasePermission):
message = {"status": 1003, "msg": "无权访问2"}
def has_permission(self, request, view):
print("permission2")
return False
class MyPermission3(BasePermission):
message = {"status": 1003, "msg": "无权访问3"}
def has_permission(self, request, view):
print("permission3")
return True
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
views.py 局部配置
from ext.per import MyPermission1, MyPermission2, MyPermission3
class OrderView(APIView):
permission_classes = [MyPermission1, MyPermission2, MyPermission3] # 局部配置
def get(self, request):
return Response("OrderView-get")
2
3
4
5
6
7
8
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",
],
# 权限
"DEFAULT_PERMISSION_CLASSES": [
"ext.per.MyPermission1",
"ext.per.MyPermission2",
"ext.per.MyPermission3",
],
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 源码流程
请求一来, 肯定会执行 dispatch 方法.. 那么我们直接从disptch方法开始切入.
注意观察, 权限源码, 权限校验不通过, 抛出的异常, 异常会被 dispatch的try..except.. 捕获.
在self.initial(request, *args, **kwargs)这行语句就终止程序运行了, 视图类函数是不会执行的!!!
而回顾下认证的源码, 如果是匿名用户访问(都返回None时),视图类函数是可以执行的.. 因为该认证失败的请求不会抛异常的!
另外, 权限类里是可以对当前视图类的实例化对象进行操作的!! 来源这条语句 permission.has_permission(request, self)
"""怎么看?
self.dispatch --> self.initial --> self.check_permissions --> self.get_permissions --> self.permission_denied
(一定要理清,这些方法中的self都是视图类的实例化对象!!
想嘛,视图类继承了APIView,找不到的就去其父类APIView中找,这些方法都是APIView的实例方法.)
permission_denied --> 回顾认证的源码 --> NotAuthenticated、PermissionDenied
"""
### -- exceptions.py
class NotAuthenticated(APIException):
status_code = status.HTTP_401_UNAUTHORIZED
# 翻译: 没有提供身份验证凭据.
default_detail = _('Authentication credentials were not provided.')
# 翻译: 没有经过验证
default_code = 'not_authenticated'
class PermissionDenied(APIException):
status_code = status.HTTP_403_FORBIDDEN
# 翻译: 您没有执行此操作的权限.
default_detail = _('You do not have permission to perform this action.')
# 翻译: 没有权限
default_code = 'permission_denied'
### -- views.py
class APIView(View):
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES # 读取配置文件中关于权限的全局配置
# ▲▲▲ 很奇妙,源码中,明明是权限校验的失败,但若是匿名用户访问的,会先提示认证的错误.
def permission_denied(self, request, message=None, code=None):
# 这个if判断,回顾下认证的源码,就能理清啦!!
if request.authenticators and not request.successful_authenticator:
raise exceptions.NotAuthenticated()
# ★★★ 这个message、code是permission_denied接受的实参!! 该实参是从权限类中获取的!!!
raise exceptions.PermissionDenied(detail=message, code=code)
def get_permissions(self):
return [permission() for permission in self.permission_classes]
def check_permissions(self, request):
for permission in self.get_permissions():
# 当某个权限类的has_permission方法返回值是False时,执行if语句体里的内容.
# 简单来说:循环权限组件,不合适的就报错.
"""
permission.has_permission(request, self)
这就是为啥在编写权限类时要重写has_permission方法!!并且重写的该方法要返回bool值!
传入的self是当前的视图类的实例对象! 这个设计细品,能感觉很巧妙.
◆ 这意味着,
So, obj = 视图类(), 此时obj通过点语法能取到的东西,权限类的has_permission方法中的view参数都可以取到!!
比如 在视图类里定义一个类变量name='dc' 在权限类的has_permission方法中就可以通过view.name就可以取到!
>> 类变量、__init__里给类实例化变量定制的属性、类中的绑定方法、类中的非绑定方法等. 复习下oop的知识就清楚了!!
"""
if not permission.has_permission(request, self):
self.permission_denied( # -- 该方法就是为了抛出异常!!
request,
# ★★★ 在权限类中读取message类变量和code类变量的值!!为自定义报错信息作的准备.
# 设置code是500,报错时返回的状态码不是500哦!!为啥?该code涉及到异常类的处理逻辑了,暂时不管.
"""
用到了反射,So,可以在权限类里 定义类变量message 或者 在has_permission定义self.message
这样的话,权限验证失败显示的就是我们设置的中文信息啦!
"""
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
def initial(self, request, *args, **kwargs):
"""
认证组件的过程:
会循环执行每个认证类的authticate方法,一旦某个认证类认证失败,会抛出异常;成功会给request.user/request.auth赋值.
(抛出的异常会被dispatch中的try..except..捕获到 -- 往下的self.check_permissions(request)代码就不会执行啦)
★ 若能执行权限校验的代码,`self.check_permissions(request)`,只能是两种情况之一:
简而言之,但凡认证报错就没权限啥事了.
1> 认证成功啦,request.user/rquest.auth中存在当前登陆用户的认证信息.
2> 所有的认证都没通过,但都未抛出异常,返回的都是None,表明是以匿名用户的身份进行的访问.
即此时request.user/rquest.auth的值都为None.
"""
self.perform_authentication(request) # 认证
self.check_permissions(request) # 权限
self.check_throttles(request)
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
try:
self.initial(request, *args, **kwargs)
if request.method.lower() in self.http_method_names:
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
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
补充: 对permission_denied方法里的, if request.authenticators and not request.successful_authenticator:
作分析!
def permission_denied(self, request, message=None, code=None):
if request.authenticators and not request.successful_authenticator:
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied(detail=message, code=code)
首先,要清楚的明白!!
for循环中, 《当某个权限类的has_permission方法返回值是False时》,表明权限校验失败啦,就会调用permission_denied方法!!
权限校验失败了,需要抛出错误. 抛出啥错误呢?
有两种选择,其一是 没有提供身份验证凭据; 其二是 您没有执行此操作的权限.
这都有取决于permission_denied方法中的那个if判断!!
if判断中用and连接了两个条件,同真才为真!eg: 1 and 2 --> 2 ; 0 and 1 --> 0
第一个条件: request.authenticators
request.authenticators是封装到reques中的[认证类对象1,认证类对象2,认证类对象3.]
1> 若全局或局部配置的认证的值是[],那么request.authenticators的值是(). 有两点依据,
"[i for i in ()] 其结果为 []"、
"Request的__init__里写了self.authenticators = authenticators or () # [] or () --> ()"
2> 若全局和局部都没有配置认证,调用的是 drf组件中默认的配置,里面是有两个认证类的!
虽然我们没仔细研究这个认证类,但我可以肯定的是不做其他额外的处理,它肯定是匿名用户!
返回的都是None!(我在源码中打印进行过验证,确信是这么一回事.)
第二个条件: not request.successful_authenticator
该方法的源码如下:
@property
def successful_authenticator(self):
# -- 该方法返回用于对请求进行身份验证的身份验证实例类的实例,或“None”.
if not hasattr(self, '_authenticator'):
with wrap_attributeerrors():
self._authenticate() # 该方法就是具体认证逻辑的方法!! 回忆下,上一篇是在Request的user方法中调用的!!
return self._authenticator
既然如此,再来回顾下,认证的具体过程:
认证成功,它将 <认证成功的那个认证类的实例化对象进行了赋值> self._authenticator = authenticator
若认证失败抛出错误,或者全返回None,匿名用户访问 最后都会执行 self._authenticator = None
▲ 而若认证失败抛出异常是不会执行到权限的,所以 只能是 认证成功 或者 匿名用户访问!
def _authenticate(self):
for authenticator in self.authenticators: # authentication_classes = []
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated() # :-D 认证失败 - 抛出异常 self._authenticator = None
raise
if user_auth_tuple is not None:
self._authenticator = authenticator # ψ(`∇´)ψ 认证成功,赋值为相关的那个认证类的实例
self.user, self.auth = user_auth_tuple
return
self._not_authenticated() # :-D "认证失败 - 匿名用户访问" 以及 "未进行认证"
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
综合对这两个条件的分析:
def permission_denied(self, request, message=None, code=None):
if request.authenticators and not request.successful_authenticator:
# 有认证且认证失败(报错/匿名访问) 执行该if代码体代码
# 按照代码逻辑,认证失败报错后续是不会进行权限校验的,So,这里是 有认证/认证进行了配置 且匿名访问才执行if代码体代码!
""" 匿名用户
- self.request.user
AnonymousUser
- self.request.auth
None
- self.request.authenticators 认证类的实例化对象(drf组件默认的认证配置就是这两个认证类
[<rest_framework.authentication.SessionAuthentication object at 0x1107a3dc0>,
<rest_framework.authentication.BasicAuthentication object at 0x1107a3580>]
- self.request._authenticator
None
"""
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied(detail=message, code=code)
- 不进行认证
1>认证的局部配置,在视图类里写authentication_classes = []
2>不写认证的局部配置,写认证的全局配置 REST_FRAMEWORK = {DEFAULT_AUTHENTICATION_CLASSES:[]}
- 匿名访问
3>认证的全局和局部配置都不写,就使用drf组件默认的认证配置 -- 相当于全局的认证配置,且认证结果是匿名访问
4>进行了认证的配置(不管是全局还是局部的),认证的结果是匿名用户访问
★★★
- 前两种情况是说,我不进行认证,那么if判断的条件1 request.authenticators的值为(),bool值为False
意味着,if判断体里的语句不会执行,往后直接运行 raise exceptions.PermissionDenied(detail=message, code=code)
- 后两种情况,恰好满足if条件判断为真!
意味着,会执行if判断体里的语句 raise exceptions.NotAuthenticated()
<其实我还是有个疑惑的>
经过实验 0 and self.xxx (其中xxx是self中不存在的属性) 其结果为0,不会报错
意味着self.xxx不会优先于and语句执行 那么此处为何不写成
request.authenticators and not request._authenticator
可能是drf的开发者想保证
request一定有_authenticator这个属性.提高程序的健壮性和可读性!
单独提供一个successful_authenticator接口!
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
# 自定义(或关系)
# 源码里默认的且逻辑
#-- self.dispatch --> self.initial --> self.check_permissions
def check_permissions(self, request):
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
2
3
4
5
6
7
8
9
10
# 自定义的或逻辑
在视图类里重写check_permissions方法, 会优先调用视图类里的!!
# 第一版
第一版: 所有权限的校验不通过, 错误信息提示的是 最后一个 权限的错误信息设置..(在下方示例中就是 MyPermission3.)
class OrderView(APIView):
permission_classes = [MyPermission1, MyPermission2, MyPermission3]
def get(self, request):
return Response("OrderView-get")
def check_permissions(self, request):
for permission in self.get_permissions():
if permission.has_permission(request, self):
return
else: # for-else else子句在for循环正常完成时执行
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
"""跟上述的代码效果是等效的.
def check_permissions(self, request):
flag = False
for permission in self.get_permissions():
if permission.has_permission(request, self):
flag = True
return
if not flag:
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
"""
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
# 第二版
第二版: 所有权限的校验不通过, 错误信息提示的是 第一个 权限的错误信息设置..
class OrderView(APIView):
permission_classes = [MyPermission1, MyPermission2, MyPermission3]
def get(self, request):
return Response("OrderView-get")
def check_permissions(self, request):
no_permissions_obj = []
for permission in self.get_permissions():
if permission.has_permission(request, self):
return
no_permissions_obj.append(permission)
else:
self.permission_denied(
request,
message=getattr(no_permissions_obj[0], 'message', None),
code=getattr(no_permissions_obj[0], 'code', None)
)
"""跟上述的代码效果是等效的. 只不过上面的理解更直接,下面这个要绕个弯.
def check_permissions(self, request):
permissions_obj = self.get_permissions()
for permission in permissions_obj:
if permission.has_permission(request, self):
return
else:
self.permission_denied(
request,
message=getattr(permissions_obj[0], 'message', None),
code=getattr(permission[0], 'code', None)
)
"""
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
# 最终版
若很多视图类都需要重写该方法来自定义或逻辑, 那么可以通过继承来简化代码!!
★这样的话, 若视图类OrderView继承APIView, 权限的验证就是且的关系; 若继承BaseApiView, 权限的校验就是或的关系!!
class BaseApiView(APIView):
def check_permissions(self, request):
no_permissions_obj = []
for permission in self.get_permissions():
if permission.has_permission(request, self):
return
no_permissions_obj.append(permission)
else:
self.permission_denied(
request,
message=getattr(no_permissions_obj[0], 'message', None),
code=getattr(no_permissions_obj[0], 'code', None)
)
class OrderView(BaseApiView):
permission_classes = [MyPermission1, MyPermission2, MyPermission3]
def get(self, request):
return Response("OrderView-get")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 应用场景
用户登陆后, 访问接口会进行有关角色的权限校验
举个例子, 用户有三种角色, 总监/经理/员工 ,
视图A中的所有接口只能总监或经理访问; 视图B中的所有接口只能经理或员工访问!!
关键代码如下:
# --- --- --- 数据库
class UserInfo(models.Model):
"""用户表"""
role = models.IntegerField(verbose_name="角色", choices=((1, '总监'), (2, '经理'), (3, '员工')), default=3)
username = models.CharField(verbose_name="用户名", max_length=32)
password = models.CharField(verbose_name="密码", max_length=64)
token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True)
# --- --- --- 认证
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:
# !!登陆认证成功后,request.user里是有用户信息的,后续可以通过request.user.role取到用户的角色!!
return user_object, token
return
def authenticate_header(self, request):
return "API"
# --- --- --- 权限
# 若是or或关系,返回True,表明权限校验成功、返回False,会将错误信息收集起来,当所有权限条件都无一通过,权限校验失败.
class UserPermission(BasePermission):
message = {"status": 1003, "msg": "不是员工,无权访问"}
def has_permission(self, request, view):
if request.user.role == 3:
return True
return False
class ManagerPermission(BasePermission):
message = {"status": 1003, "msg": "不是经理,无权访问"}
def has_permission(self, request, view):
if request.user.role == 2:
return True
return False
class BossPermission(BasePermission):
message = {"status": 1003, "msg": "不是主管,无权访问"}
def has_permission(self, request, view):
if request.user.role == 1:
return True
return False
# --- --- --- 视图类 此处继承的是BaseApiView,或关系 注意哦,先认证再权限校验!
class OrderView(BaseApiView):
authentication_classes = [QueryParamsAuthentication, HeaderAuthentication, NoAuthentication]
permission_classes = [BossPermission, ManagerPermission] # -- 主管 或 经理才能访问该接口
def get(self, request):
return Response("OrderView-get")
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
# 思考题
Q: 如何替换drf的request对象?
A: 在视图类中重写 initialize_request 方法!!
Q: ★ drf 中的认证、权限组件 跟 django中的中间件有何关系?
A: drf的认证、权限 的执行顺序是 落后于 django的中间件的! 至于什么时候用谁,看具体的应用场景.
rbac的项目里“用户-角色-权限”,可以用django的中间件做登陆认证和权限的校验.
设置白名单,比如登陆接口,process_request和process_view直接返回None
登陆过程中会查询数据库将该用户的所有能访问的接口,登陆成功,将结果存放到session中.
比如:{"per_url_name":[...能访问的url的name]}
访问其他接口时,同样要经过中间件,
- process_request里判断session里有没有"per_url_name"这个key,有,则证明已经登陆成功啦;
- 路由匹配成功,到process_view时,request中已经有所有路由信息啦!!
request.resolver_match.url_name取到当前匹配成功的这个路由的name.
看该路由的name 是否在request.session["per_url_name"]中,有,该用户则有权限访问该接口!!
▼ 注:路由匹配不成功,不会执行process_view
理清下述的生命周期!! ★★★
请求进来,先经过中间件
1.依次执行完所有中间件的process_request方法
2.进行路由匹配,找到了路由对应的视图函数!!(FBV) 若是drf的CBV,找到的是那个闭包中的函数view 注:此view是免除了csrf认证的!
(▼ 注意!该函数此时是没有执行的!!)
3.依次执行所有中间件的process_view方法
4.接着,执行闭包中的函数view view()
闭包中的view干了几件事
1> 创建CBV视图类的实例化对象 self = cls()
2> 执行self.disptch 注:视图类是继承APIView的,self中没有disptch,APIView中有
2.1> 请求封装
2.2> 进行认证、权限的处理
5.最后,执行所有中间件的process_response方法
""" <我实验过了,实验时,只加了一个中间件,根据打印结果,是对的!!>
实验时,访问的是这样的接口 GET http://127.0.0.1:8000/order/?token=dd5df47b-87ba-435a-8430-97bdada16c71
M1的p_req # process_request
M1的p_v # process_view
QueryParams # 认证
permission1 # 权限
permission2
M1的p_res # process_response
"""
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
# 模拟
此时模拟的是 权限校验的源码流程!! 权限校验是 且的关系!
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,认证失败")
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
per.py
class BasePermission:
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
return True
class MyPermission1(BasePermission):
message = {"status": 1003, "msg": "无权访问1"} # 自定义权限的错误提示信息
def has_permission(self, request, view):
print("permission1")
print(view.tip, view.say(), view.height, view.width) # ◆◆◆
return True
class MyPermission2(BasePermission):
message = {"status": 1003, "msg": "无权访问2"}
def has_permission(self, request, view):
print("permission2")
return False
# return True
class MyPermission3(BasePermission):
message = {"status": 1003, "msg": "无权访问3"}
def has_permission(self, request, view):
print("permission3")
return True
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
run.py
from auth import QueryParamsAuthentication, HeaderAuthentication, NoAuthentication
from per import MyPermission1, MyPermission2, MyPermission3
class View:
@classmethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
return self.dispatch(request, *args, **kwargs)
return view
def dispatch(self, request, *args, **kwargs):
pass
class APIView(View):
authentication_classes = [QueryParamsAuthentication, HeaderAuthentication]
permission_classes = [MyPermission1, MyPermission2]
@classmethod
def as_view(cls, **initkwargs):
# view = super(APIView, cls).as_view()
view = super().as_view(**initkwargs)
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("dispatch捕获的报错>>:", 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) # 进行认证
self.check_permissions(request) # 进行权限校验
def perform_authentication(self, request):
request.user
def check_permissions(self, request):
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
def get_permissions(self):
return [permission() for permission in self.permission_classes]
# 很奇妙,源码中,明明是权限校验的失败,但若是匿名用户访问的,得先提示认证的错误.
def permission_denied(self, request, message=None, code=None):
print(request.user, request.auth, request._authenticator)
if request.authenticators and not request._authenticator:
# 有认证且认证失败(报错/匿名访问)抛出异常
# 按照代码逻辑,认证失败报错后续是不会进行权限校验的,So,这里是不希望匿名用户访问的.
# 执行到这里,request.user, request.auth, request._authenticator的值肯定为 None None None
raise Exception("not_authenticated.")
raise Exception(f"permission_denied: {message}-{code}")
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
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._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
def _not_authenticated(self):
self._authenticator = None
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]
# authentication_classes = []
permission_classes = [MyPermission1, MyPermission2, MyPermission3]
def __init__(self, **kwargs):
self.tip = "perfect"
for k, v in kwargs.items():
setattr(self, k, v)
# 当时我突然思考一个问题,若每次类实例化都需要调用一个很复杂的函数得到其返回的值,eg: self.xx = foo()
# 那么该foo函数的执行其实可以放到类变量中,执行类定义代码时,该函数是会自动执行的.测试了下:
"""
def func1():
return 123
class Foo:
xx = func1()
print(Foo.__dict__.get("xx", "无")) # 123
"""
def say(self):
return "hello"
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()
# 111是模拟路由参数传递! **{"height": 100, "width": 200}是InfoView构造方法的实参
print(InfoView.as_view(**{"height": 100, "width": 200})(request, 111))
""" 根据上述的代码分析,基于这几点逻辑
1.权限校验是且的关系,视图类设置了[MyPermission1, MyPermission2, MyPermission3]三个权限类,其中第二个权限类的校验是不通过的
So,肯定到不了视图函数那
2.视图类没有配置认证,认证用的是APIView类里设置的,[QueryParamsAuthentication, HeaderAuthentication]
因为没有认证类NoAuthentication,So,可能会出现匿名用户访问
打印的结果只可能出现下面三种情况之一:
permission1
perfect hello 100 200
permission2
dc-query 1 <auth.QueryParamsAuthentication object at 0x7fafb2314880>
dispatch捕获的报错>>: permission_denied: {'status': 1003, 'msg': '无权访问2'}-None
None
permission1
perfect hello 100 200
permission2
dc-header 1 <auth.HeaderAuthentication object at 0x7fa00b3198b0>
dispatch捕获的报错>>: permission_denied: {'status': 1003, 'msg': '无权访问2'}-None
None
permission1
perfect hello 100 200
permission2
None None None
dispatch捕获的报错>>: not_authenticated.
None
"""
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