一、权限组件的使用
1、自定义权限
要实现自定义权限,需要重写BasePermission
并实现以下方法中的一个或两个
.has_permission(self, request, view)
.has_object_permission(self, request, view, obj)
如果请求被授予访问权限,方法应该返回True
,否则返回False
。
@six.add_metaclass(BasePermissionMetaclass) class BasePermission(object): """ A base class from which all permission classes should inherit. """ def has_permission(self, request, view): """ Return `True` if permission is granted, `False` otherwise. """ return True def has_object_permission(self, request, view, obj): """ Return `True` if permission is granted, `False` otherwise. """ return True
权限组件需要与认证组件进行配合,因为一个用户只有认证登录,拿到用户信息。此时根据认证后的用户拿到相应的权限进行操作,下面是基于rbac权限控制如何利用
restframework中的权限组件。
from django.conf import settings import re from rest_framework.permissions import BasePermission class Permission(BasePermission): message = "无权限访问" def has_permission(self, request, view): """ Return `True` if permission is granted, `False` otherwise. """ permissions_dict = {} menus_dict = {} """ 从数据库拿到该用户的所有权限, request.user是认证后拿到的元组第一个值 """ permissions_queryset = request.user.roles.filter( permissions__url__isnull=False).values( 'permissions__id', 'permissions__url', 'permissions__title', 'permissions__parent_id', 'permissions__action__code', 'permissions__menu_id', 'permissions__menu__title', 'permissions__menu__icon', 'permissions__menu__position').distinct() """ 将用户的权限进行数据结构的调整 { '/index.html': ['GET','POST','DEL','EDIT], r'/detail-(d+).html': ['GET','POST','DEL','EDIT], } """ for row in permissions_queryset: if row["permissions__url"] in permissions_dict: permissions_dict[row["permissions__url"]].append( row["permissions__action__code"]) else: permissions_dict[row["permissions__url"]] = [ row["permissions__action__code"], ] """ 初始化用户菜单,也就是该用户可以访问的菜单 """ for row in permissions_queryset: menu_id = row["permissions__menu_id"] if not menu_id: continue if menu_id not in menus_dict: menus_dict[row["permissions__menu__position"]] = { "id": row["permissions__menu_id"], "title": row["permissions__menu__title"], "icon": row["permissions__menu__icon"], "children": [ { 'id': row['permissions__id'], 'title': row['permissions__title'], 'url': row['permissions__url'] } ] } else: menus_dict[row["permissions__menu__position"]]["children"].append( { 'id': row['permissions__id'], 'title': row['permissions__title'], 'url': row['permissions__url'] } ) """ 将该用户的权限赋值给视图,这样在视图中可以拿到对应的权限, 传给前端进行按钮级别的权限验证 """ view.permissions_dict = permissions_dict """ 将该用户的菜单赋值给视图,这样在视图中可以拿到对应的菜单, 传给前端进行左侧菜单的初始化 """ view.menus_dict = menus_dict """ 过滤白名单 """ for pattern in settings.RBAC_NO_AUTH_URL: if re.match(pattern, request.path_info): return None if not permissions_dict: return False # 请求url与用户拥有的权限进行匹配 """ /crm/menus {'/rights': ['get'], '/user': ['get', 'post'], '/roles': ['get']} """ flag = False for pattern, code_list in permissions_dict.items(): upper_code_list = [item.upper() for item in code_list] request_permission_code = request.method if re.match(pattern, request.path_info): if request_permission_code in upper_code_list: permission_code_list = upper_code_list # 将用户角色拥有的请求方式赋值给视图 view.PERMISSION_CODE_LIST_KEY = permission_code_list flag = True break if not flag: return False return True
当认证类完成后,就可以进行全局注册或者局部注册:
- 全局注册
REST_FRAMEWORK = { # 全局使用的认证类 "DEFAULT_AUTHENTICATION_CLASSES":['crm.utils.auth.AuthToken',], #全局权限类 "DEFAULT_PERMISSION_CLASSES": ['rbac.permissions.Permission', ], }
值得注意的是,在全局权限中使用了认证过的用户,所以必须要有认证类,这样才会针对某个用户进行权限的控制,认证相关请参考:https://www.cnblogs.com/shenjianping/p/11387324.html。
其次,登录视图中不需要认证和权限,所以应该免认证和权限验证,只需要在相应的视图中进行配置:
class LoginView(APIView): authentication_classes = [] # 登陆页面免认证 permission_classes = [] #登录免权限 def post(self,request,*args,**kwargs) pass ...
- 局部注册
只需要在每一个视图中进行配置即可:
from rbac.permissions import Permission class UserModelView(GenericViewSet): permission_classes = [Permission,] #局部权限配置 def list(self,request,*args,**kwargs) pass ...
2、内置权限
- AllowAny
允许不受限制的访问,而不管该请求是否已通过身份验证或未经身份验证
class AllowAny(BasePermission): """ Allow any access. This isn't strictly required, since you could use an empty permission_classes list, but it's useful because it makes the intention more explicit. """ def has_permission(self, request, view): return True
- IsAuthenticated
拒绝任何未经身份验证的用户的权限,并允许其他权限
class IsAuthenticated(BasePermission): """ Allows access only to authenticated users. """ def has_permission(self, request, view): return bool(request.user and request.user.is_authenticated)
- IsAuthenticatedOrReadOnly
允许经过身份验证的用户执行任何请求。只有当请求方法是“安全”方法(GET
, HEAD
或 OPTIONS
)之一时,才允许未经授权的用户请求
class IsAuthenticatedOrReadOnly(BasePermission): """ The request is authenticated as a 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_authenticated )
- IsAdminUser
除非user.is_staff
为True
,否则IsAdminUser
权限类将拒绝任何用户的权限,在这种情况下将允许权限
class IsAdminUser(BasePermission): """ Allows access only to admin users. """ def has_permission(self, request, view): return bool(request.user and request.user.is_staff)
二、源码剖析
在restframework中对View类封装成了APIView,而APIView中主要是dispatch方法的不同,在其方法中进行了request的重构以及加入一些组件(认证组件、权限组件等)。
def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs #rest-framework重构request对象 request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method #这里和CBV一样进行方法的分发 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
执行self.initial(request, *args, **kwargs)中:
def initial(self, request, *args, **kwargs): """ ... # Ensure that the incoming request is permitted self.perform_authentication(request) #进行认证 self.check_permissions(request) #权限相关 self.check_throttles(request)
执行self.check_permissions(request)中:
def check_permissions(self, request): """ Check if the request should be permitted. Raises an appropriate exception if the request is not permitted. """ for permission in self.get_permissions(): #如果没有权限抛出异常 if not permission.has_permission(request, self): self.permission_denied( request, message=getattr(permission, 'message', None) )
在这里进行权限的检验,循环所有的权限类对象(self.get_permissions())。
def get_permissions(self): """ Instantiates and returns the list of permissions that this view requires. """ return [permission() for permission in self.permission_classes]
所以,可以在视图中局部配置permission_classes这样的权限类的列表,另外循环的每一个权限类中必须实现has_permission方法,如果有权限就返回True,如果没有就返回false,
如果返回False就会执行check_permissions中的permission_denied方法,会抛出一个异常。
def permission_denied(self, request, message=None): """ If request is not permitted, determine what kind of exception to raise. """ if request.authenticators and not request.successful_authenticator: raise exceptions.NotAuthenticated() raise exceptions.PermissionDenied(detail=message)
当然自己也是可以更改与异常关联的错误消息,直接在自定义权限类实现消息属性。
from rest_framework.permissions import BasePermission class Permission(BasePermission): message = "无权限访问" ...
参考文档:https://www.django-rest-framework.org/api-guide/permissions/