Django REST Framework 学习笔记(十二):权限组件

  • Title(EN): Django REST Framework Learning Notes (12): Permissions
  • Author: dog2

基本信息

  • 源码 rest_framework.permissions
  • 官方文档 API Guide - Permissions
  • 本文demo代码Github

源码分析

入口

rest_framework.views.APIViewdispath(self, request, *args, **kwargs)下手,dispath方法内 self.initial(request, *args, **kwargs) 进入三大认证

  • 认证组件 self.perform_authentication(request)
    • 校验用户:游客、合法用户、非法用户
    • 游客:代表校验通过,直接进入下一步校验(权限校验)
    • 合法用户:代表校验通过,将用户存储在request.user中,再进入下一步校验(权限校验)
    • 非法用户:代表校验失败,抛出异常,返回403权限异常结果
  • 权限组件 self.check_permissions(request)
    • 校验用户权限:必须登录、所有用户、登录之后读写,游客只读、自定义用户角色
    • 认证通过:可以进入下一步校验(频率认证)
    • 认证失败:抛出异常,返回403权限异常结果
  • 频率组件 self.check_throttles(request)
    • 限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s)
    • 没有达到限次:正常访问接口
    • 达到限次:限制时间内不能访问,限制时间达到后,可以重新访问

本文介权限组件。

rest_framework.views.APIView.initial()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)

# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg

# Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme

# Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_permissions(request) # 权限
self.check_throttles(request)

rest_framework.views.APIView.check_permissions()

1
2
3
4
5
6
7
8
9
10
def check_permissions(self, request):
# 遍历权限对象列表得到一个个权限对象(权限器),进行权限认证
for permission in self.get_permissions():
# 权限类一定有一个has_permission权限方法,用来做权限认证的
# 参数:权限对象self、请求对象request、视图类对象
# 返回值:有权限返回True,无权限返回False
if not permission.has_permission(request, self):
self.permission_denied(
request, message=getattr(permission, 'message', None)
)

rest_framework.views.APIView.get_permissions()

1
2
3
4
5
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]

rest_framework.views.permission_classes

1
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES

api_settings.DEFAULT_PERMISSION_CLASSES

指向 settings.DEFAULTSDEFAULT_PERMISSION_CLASSES

1
2
3
4
5
6
DEFAULTS = {
# ...
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
# ...

pemissions.py

在源码permissions.py中的系统自带权限类,这里举例四个

  1. AllowAny
    • 默认是这个权限
    • 认证规则全部返还Truereturn True
    • 游客与登陆用户都有所有权限
  2. IsAuthenticated
    • 常用这个
    • 认证规则必须有登陆的合法用户:return bool(request.user and request.user.is_authenticated)
    • 有登录用户名并且认证成功
    • 游客没有任何权限,登陆用户才有权限
  3. IsAdminUser
    • 认证规则必须是后台管理用户:return bool(request.user and request.user.is_staff)
    • 游客没有任何权限,登陆用户才有权限
  4. IsAuthenticatedOrReadOnly
    • 认证规则必须是只读请求或是合法用户
      1
      2
      3
      4
      5
      return bool(
      request.method in SAFE_METHODS or
      request.user and
      request.user.is_authenticated
      )
    • 游客只读,合法用户无限制

另外,permission.py里还定义了安全的HTTP请求方法:

1
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')

权限组使用

全局使用

默认全局配置的权限类是AllowAny,在settings文件中配置:

1
2
3
4
5
6
REST_FRAMEWORK = {
# 权限类配置
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', #默认所有权限
],
}

局部使用

views.py

1
2
3
4
5
6
#只有登录后才能访问
from rest_framework.permissions import IsAuthenticated
class TestAuthenticatedAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
return APIResponse(0, 'test 登录才能访问的接口 ok')

自定义权限类

方法

  1. 创建继承BasePermission的权限类
  2. 实现has_permission方法
  3. 实现体根据权限规则 确定有无权限
    • 满足设置的用户条件,代表有权限,返回True
    • 不满足设置的用户条件,代表有权限,返回False
  4. 进行全局或局部配置

示例代码

utils.permissions.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from rest_framework.permissions import BasePermission
from django.contrib.auth.models import Group


class MyPermission(BasePermission):
def has_permission(self, request, view):
# 只读接口判断
r1 = request.method in ('GET', 'HEAD', 'OPTIONS')
# group为有权限的分组
group = Group.objects.filter(name='管理员').first()
# groups为当前用户所属的所有分组
groups = request.user.groups.all()
r2 = group and groups # group和groups必须有值
r3 = group in groups
# 读接口大家都有权限,写接口必须为指定分组下的登陆用户
return r1 or (r2 and r3)

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from rest_framework.views import APIView

from utils.permissions import MyPermission
from utils.response import APIResponse

# 游客只读,登录用户只读,只有登录用户属于 管理员 分组,才可以增删改
class TestAdminOrReadOnlyAPIView(APIView):
permission_classes = [MyPermission]
# 所有用户都可以访问
def get(self, request, *args, **kwargs):
return APIResponse(0, '自定义读 OK')
# 必须是 自定义“管理员”分组 下的用户
def post(self, request, *args, **kwargs):
return APIResponse(0, '自定义写 OK')

urls.py

1
2
3
4
5
6
7
from django.urls import path

from . import views

urlpatterns = [
path('test3/', views.TestAdminOrReadOnlyAPIView.as_view()),
]

Postman测试

  • get请求:加不加Authorization,都可以读
  • post请求:不加Authorization,就不能进行写操作,必须要写Authorization