Django restframework加Vue打造前后端分离的网站(八)权限控制
在该系列第六篇文章中,已经介绍了认证和基于登录后的权限。实际上会有更多的权限许可的类型,比如我打算设置3个角色:管理员、登录用户、匿名用户,对于管理员来说可以做任何操作,对于登录用户来说可以做大部分操作但是比较关键敏感的数据则没有权限,而匿名用户则只能对部分数据read-only。也会有这样的:对于某个项目只允许一部分人;对于项目model某个用户只有更新权限不能创建,等等。
该文章分三个部分: 根据用户类型设定权限,根据特定业务对象设定权限,根据django的默认细分权限。
一) 根据用户类型设定权限
在django的默认user model中有三个字段:is_active, is_staff, is_superuser。若没有特别的分类,就可以直接用这三个类型。restframework.permission中也是有用到这几个字段,比如IsAdminUser则是用了is_staff。
我是打算is_active(是否激活)为true时在登录后相当于is_authenticated为true,并为is_staff(是否工作人员)和is_superuser(是否超级用户)分别设定一种权限类别。超级用户应具有所有权限,于是在设置其他权限时也会给超级用户添加许可。
在utils文件夹下新建文件permission.py,如下:
from rest_framework.permissions import BasePermission
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
class IsStaffUser(BasePermission):
"""
Allows access only to staff or super users.
"""
def has_permission(self, request, view):
return bool(request.user and
request.user.is_staff or
request.user.is_superuser)
class IsStaffUserOrReadOnly(BasePermission):
"""
The request is authenticated as a staff or super user, or is a read-only request.
"""
def has_permission(self, request, view):
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.is_staff or
request.user.is_superuser
)
class IsSuperUser(BasePermission):
"""
Allows access only to super users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_superuser)
class IsSuperUserOrReadOnly(BasePermission):
"""
The request is authenticated as a super user, or is a read-only request.
"""
def has_permission(self, request, view):
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.is_superuser
)
然后我可以设定只有超级用户才可以更新用户数据:
from django.contrib.auth.models import User
from rest_framework import viewsets
from .serializers import UserSerializer
from utils.permission import IsSuperUserOrReadOnly
class UserViewSet(viewsets.ModelViewSet):
"""
get:
Return all users.
post:
Create a new user.
put:
Update a user.
patch:
Update one or more fields on an existing user.
delete:
Delete existing user.
"""
# 这里设定只允许超级用户;若设定IsAuthenticatedOrReadOnly则是登录后的都可以修改
permission_classes = [IsSuperUserOrReadOnly]
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
二) 根据特定业务对象设定权限
这里就需要用到has_object_permission了。has_permission和has_object_permission的区别是:前者是只允许老师这种用户组织考试,后者是只允许1班的老师阅卷1班的学生。
假设现在有多个项目也有多个员工,只允许项目内员工编辑该项目,而不允许编辑别的项目。
先更新Project的model,添加一个owne的字段,表示可设置项目允许的用户列表。
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Project(models.Model):
PROJECT_TYPES = (
("internal", "internal"),
("public", "public")
)
name = models.CharField(max_length=100)
project_type = models.CharField(choices=PROJECT_TYPES, default=PROJECT_TYPES[0][0], max_length=100)
create_time = models.DateTimeField(default=timezone.now)
update_time = models.DateTimeField(default=timezone.now)
owner = models.ManyToManyField(User, blank=True)
def __str__(self):
return self.name
此时迁移数据库修改
python manage.py makemigrations
python manage.py migrate
再在permission.py中添加一个新的类: IsSpecifiedProjectOrReadOnly, 设定项目指定用户和超级用户可访问。刚才添加的owner便可用project这个model筛选出来。
...
from projects.models import Project
...
class IsSpecifiedProjectOrReadOnly(BasePermission):
"""
The request user is authenticated to specified project, or super user, or is a read-only request.
"""
def has_object_permission(self, request, view, obj):
if obj._meta.object_name == "Project":
project_id = obj.id
else:
project_id = obj.project_id
allow_owners = []
for o in Project.objects.filter(id=project_id)[0].owner.values():
allow_owners.append(o["id"])
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.id in allow_owners or
request.user.is_superuser
)
然后在modules/projects/view.py中添加新的权限类型
from .models import Project
from .serializers import ProjectSerializer
from rest_framework import generics
from utils.permission import IsSpecifiedProjectOrReadOnly, IsStaffUserOrReadOnly
class ProjectList(generics.ListCreateAPIView):
"""
get:
Return all projects.
post:
Create a new project.
"""
permission_classes = [IsStaffUserOrReadOnly] # here
queryset = Project.objects.all().order_by("id")
serializer_class = ProjectSerializer
class ProjectDetail(generics.RetrieveUpdateAPIView):
"""
get:
Return a project instance.
put:
Update a project.
patch:
Update one or more fields on an existing project.
"""
permission_classes = [IsSpecifiedProjectOrReadOnly] # here
queryset = Project.objects.all()
serializer_class = ProjectSerializer
此时上面设置的权限类型便可生效。
三) 根据django的默认细分权限
为了保证前后端的权限类型一致,有时需要由后端给出某个用户的具体的细分权限列表,比如对project的模块是否为可读、可写、可更新和可删除。前端可以根据该权限列表决定是否展示页面和控件。不过对于是否允许操作有两种:一种是前端根据后端的权限直接提示用户,第二种是前端仍然发送请求给后端并展示后端的提示。
用这种方式,则不需要定义view.py中的permission_classes。
在settings.py中加上定义
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.DjangoModelPermissions',
),
...
}
此时会判断用户是否具有某模块的特定权限,以此决定是否允许操作。该权限在数据库中的auth_user_user_permission表中生成记录,可以更新user的信息来更新对应权限,也可以在admin的用户管理页面中添加细分的权限。
不过对于super user来说,即使没有添加权限列表,该用户也是可以做所有操作的,比如用python manage.py shell中可以看到:
>>> from django.contrib.auth.models import User
>>> user_obj = User.objects.get(username='super')
>>> user_obj.get_all_permissions()
{'contenttypes.change_contenttype', 'auth.change_user', 'contenttypes.view_contenttype', 'auth.view_group', 'auth.change_group', 'authtoken.delete_token', 'auth.delete_user', 'contenttypes.add_contenttype', 'sessions.view_session', 'admin.add_logentry', 'authtoken.change_token', 'contenttypes.delete_contenttype', 'admin.view_logentry', 'authtoken.add_token', 'projects.add_project', 'auth.add_permission', 'auth.add_user', 'auth.view_user', 'auth.add_group', 'projects.delete_project', 'authtoken.view_token', 'sessions.change_session', 'projects.change_project', 'projects.view_project', 'sessions.add_session', 'admin.delete_logentry', 'auth.delete_permission', 'auth.view_permission', 'admin.change_logentry', 'auth.change_permission', 'auth.delete_group', 'sessions.delete_session'}
view代表get,add代表create,change代表put和patch,delete代表delete。
不过需要注意的是,用DjangoModelPermissions并没有限定view的权限,即使是个匿名用户,也可以查看。如果需要限定get的权限,需要新增一个类,并在settings.py中指向这个类,如下
class DjangoModelPermissionsWithRead(DjangoModelPermissions):
perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'],
'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'],
}
settings.py中就变成
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'utils.permission.DjangoModelPermissionsWithRead',
),
...
}
到此三种设置权限的方式就介绍完了。