задний план
Система разрешений неизбежна в фоновом режиме В этой статье представлена наша схема реализации системы разрешений.
Прежде чем поделиться, давайте кратко представим наш платформенный бизнес. Мы являемся отделом качества, и наша платформа объединяет несколько бизнес-подразделений, поэтому нам необходимо достичь:
- Многопользовательский
- многопроектный
- 3 роли
Разные пользователи имеют роли в проектах разных отделов, и каждая роль имеет разные права доступа к разным интерфейсам, например:
- Только администратор может удалить данные
- Все пользователи имеют права просмотра данных
- Только операторы могут изменять данные
Выше приведены требования упрощенной разрешительной системы.Поговорим о плане внедрения.
Дизайн и реализация
существуетDjango - сериализация модели возвращает естественное значение первичного ключаВ этой статье мы узнали о модуле сериализации DRF.Помимо сериализации, DRF также инкапсулирует множество полезных функций, таких как наша текущая платформаAPIView
Он унаследован от DRFAPIView
класс, а также класс пагинации (Pagination) и класс управления разрешениями (Permission) и так далее.
Наша схема реализации контроля разрешений заимствована из DRF.DjangoModelPermission
своего рода.
Модуль разрешений Django на самом деле уже имеет
User
,Group
,Permission
Причина, по которой модель данных и отношение ассоциации не используют официальные разрешения и не используют напрямую модуль разрешений DRF, заключается в том, что оба они основаны на CURD модели данных, которую можно настроить, но настройка и миграция данных относительно сложны. Дело в том, что бизнес не нуждается в доработке.С гибкой конфигурацией разрешений, поэтому не принято.
ролевые отношения
Пользователи имеют разные роли в разных проектах, а проект также имеет несколько пользователей, поэтому отношения между пользователями, проектами и ролями:Пользователь и элемент составляют объединенный первичный ключ, соответствующий роли.
Проектные отношения-роль:
Отношения проект-пользователь-роль:
В таблицу можно вывести роли пользователя в разных продуктах, а также два измерения информации обо всех пользователях в продукте и соответствующие разрешения, что удобно для настройки ролей из двух измерений.
модель данных
from django.db import models
from django.conf import settings
from myapp.codes import role
class UserProjectRole(models.Model):
"""用户-项目-角色关系表"""
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
project = models.ForeignKey('myapp.Project', on_delete=models.CASCADE)
role = models.IntegerField(default=role.GUEST)
class Meta:
db_table = 'myapp_user_project_role'
unique_together = ['user', 'project']
Роли и сеансы
Хотя у пользователя разные роли в разных проектах, он может одновременно получить доступ только к одному проекту, поэтому текущий продукт и соответствующая роль могут быть сохранены непосредственно в сеансе пользователя, что сокращает процесс частых запросов к базе данных.
Код
def get_or_create_role(user_id, project_id, session, default_role=role.GUEST):
"""根据session获取当前用户的角色"""
role = session.get('role')
if not role:
role_rel_obj, _ = AuthGroup.objects.get_or_create(
user_id=user_id,
project_id=project_id,
default={'role': default_role})
session['role'] = role_rel_obj.role
return role
инициализация роли
Когда пользователь выбирает элемент в первый раз,myapp_user_project_role
Часть данных вставляется в таблицу.
Примечательно, что Джангоauth_user
В таблице есть готовые поля, по которым можно определить, является ли пользователь администратором. я используюauth_user.is_staff == 1
Для администраторов права администратора доступны только через Django.admin
Сайт изменен, чтобы пользователи-администраторы не могли быть обновлены или понижены без разбора.
Если пользователь администратор, вставьтеadmin
роль; в противном случае вставьтеguest
Роль.Operator
Роли создаются через интерфейс конфигурации.
Код
class SelectProject(MyAPIView):
def post(self, request, project_id):
"""选择项目"""
default_role = role.ADMIN if is_admin(request.user) else role.GUEST
role_rel_obj, _ = UserProjectRole.objects.get_or_create(
user=request.user,
project_id=project_id,
defaults={'role': default_role})
request.session['role'] = role_rel_obj.role
...
Авторитетные отношения
Разрешения в основном относятся к разрешениям на операции, отправляемым различным методам запроса каждым интерфейсом.
Отношения интерфейс-запрос-метод-роль:
ДРФ класс Djangomodelpermission
DjangoModelPermission
Полный исходный код доступенисходный код.
Теперь разберем реализацию этого класса.
Во-первых, это описание в строке документации: оно гарантирует, что пользователь аутентифицирован и имеет соответствующиеadd
/change
/delete
Разрешения на модели. И структура сопоставления взаимосвязи между типами запросов и разрешениями:
perms_map = {
'GET': [],
'OPTIONS': [],
'HEAD': [],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
Как видите, этот класс разрешений выполняет контроль разрешений на основе взаимосвязи между творогом модели данных и типом запроса.
Затем посмотрите на определения двух методов класса:
-
get_required_permissions
: Учитывая тип запроса, вернуть список разрешений, требуемых типом запроса. -
has_permission
: определить, есть ли у пользователя разрешение на выполнение этого запроса.
has_permission
метод в родительском классеBasePermission
определено в, возвращениеTrue
Это означает, что у него есть разрешение, иначе он будет вAPIView
Был схвачен, вернулся403
.
Используя приближенную логику, мы можем переписатьRolePermissions
своего рода.
Код
Поскольку мы напрямую контролируем различные методы запроса интерфейса, нам необходимо определить список разрешений, соответствующий каждому методу запроса. Для упрощения написания я изменил список на минимально необходимые разрешения:
class MyAPI(MyAPIView):
min_perms_map = {
'POST': role.OPERATOR,
'DELETE': role.ADMIN,
}
будетget_required_permissions
Переписано, чтобы возвращать список разрешений на основе наименьших разрешений:
def get_required_permissions(perms_map, allowed_methods, method):
"""
接收 APIView 配置的 min_perms_map 以及发送的请求方法(Method),返回允许请求的
角色列表。如果 APIView 中未对 method 进行权限配置,则视为所有角色都用户该
method 的权限。
"""
if method not in perms_map:
if method not in allowed_methods:
raise exceptions.MethodNotAllowed(method)
return list(range(1, role.GUEST + 1))
return list(range(1, perms_map[method] + 1))
has_perms
Методы определены в пользовательской модели данных Django и не могут быть переопределены. Создайте новый нормальный метод напрямуюhas_perms
Чтобы узнать, соответствуют ли разрешения, соответствующие этому запросу:
def has_perms(request, perms: list):
"""判断用户在项目中的权限"""
try:
role = get_or_create_role(request.user.pk, request.session)
if not role:
return False
if not perms or role in perms:
return True
return False
except:
return False
Установить как класс разрешений по умолчанию
так какRolePermission
Классы не инициализируются до тех пор, пока наше приложение не будет сгенерировано, поэтому их нельзя настроить вsettings.py
середина.
Мое решение состояло в том, чтобы переписатьMyAPIView
класс, унаследованный от DRFAPIView
своего рода. Настройте в этом классе:
from rest_framework.views import APIView
from myapp.permissions import RolePermissions
class MyAPIView(APIView):
permission_class = [RolePermissions]
Тогда каждый интерфейс наследуется отMyAPIView
Вот и все.
модульный тест
В случаях использования, которые не заботятся о ролях, мы можем сделатьMyAPIView
класс для записи переключателя, например в переменнойRUN_TEST
заTrue
Когда вы не настраиваетеpermission_class
Чтобы обойти ограничение вынесения решения:
class MyAPIView(APIView):
if RUN_TEST is False:
permission_class = [RolePermissions]
Суммировать
В разных компаниях используются разные схемы реализации управления доступом на основе ролей (RBAC). Для более точного управления разрешениями необходимо разработать более сложные отношения разрешений. Выберите план, который подходит вашему бизнесу.