Django的 auth 模块讲解和装饰器 permission_required 的源码流程

13 篇文章 0 订阅

Django的auth系统提供了模型级的权限控制, 即可以检查用户是否对某个数据表拥有增(add), 改(change), 删(delete)权限。
(auth系统无法提供对象级的权限控制, 即检查用户是否对数据表中某条记录拥有增改删的权限,如果需要对象级权限控制可以使用django-guardian)。每个模型默认拥有增(add), 改(change), 删(delete)权限。例如,定义一个名为『Car』model,定义好Car之后,会自动创建相应的三个permission:add_car, change_car和delete_car。Django还允许自定义permission,例如,我们可以为Car创建新的权限项:drive_car, clean_car, fix_car等等。关于自定义权限可参考Django的 auth_permission 的自定义权限

Django 的 auth 模块和其包含的三个主要 model: Permission, User, Group。 其中 User 和 Group 中定义了多对对的外键,所以涉及的数据库表如下:
auth_permissions:各种权限

auth_user:用户表
auth_user_groups:用户和组的对应关系
auth_user_user_permissions:用户和权限的对应关系

auth_group:用户组
auth_group_permissions:用户组合权限的对应关系

有了上面的铺垫,我们开始分析装饰器 permission_required 的源码:

from django.contrib.auth.decorators import login_required, permission_required
C:\Python27\Lib\site-packages\django\contrib\auth\decorators.py
def permission_required(perm, login_url=None, raise_exception=False):
    """
    Decorator for views that checks whether a user has a particular permission
    enabled, redirecting to the log-in page if necessary.
    If the raise_exception parameter is given the PermissionDenied exception
    is raised.
    """
    def check_perms(user):
        if not isinstance(perm, (list, tuple)):
            perms = (perm, )
        else:
            perms = perm
        # First check if the user has the permission (even anon users)
        if user.has_perms(perms):                    ----------------------------step 1
            return True
        # In case the 403 handler should be called raise the exception
        if raise_exception:
            raise PermissionDenied
        # As the last resort, show the login form
        return False
    return user_passes_test(check_perms, login_url=login_url)
	
	
C:\Python27\Lib\site-packages\django\contrib\auth\models.py	
class PermissionsMixin(models.Model): (该类是 AbstractUser 父类,而 AbstractUser 又是  User 的父类)
    ......
	def has_perms(self, perm_list, obj=None):
		"""
		Returns True if the user has each of the specified permissions. If
		object is passed, it checks if the user has all required perms for this
		object.
		"""
		for perm in perm_list:
			if not self.has_perm(perm, obj):            ----------------------------step 2
				return False
		return True
		
		
	def has_perm(self, perm, obj=None):
		"""
		Returns True if the user has the specified permission. This method
		queries all available auth backends, but returns immediately if any
		backend returns True. Thus, a user who has permission from a single
		auth backend is assumed to have permission in general. If an object is
		provided, permissions for this specific object are checked.
		"""
	
		# Active superusers have all permissions.
		if self.is_active and self.is_superuser:       ---------------------------step 3.0 超级用户直接返回 True
			return True
	
		# Otherwise we need to check the backends.
		return _user_has_perm(self, perm, obj)        ----------------------------step 3.1 
	
	
def _user_has_perm(user, perm, obj):
    """
    A backend can raise `PermissionDenied` to short-circuit permission checking.
    """
    for backend in auth.get_backends():          ----------------------------step 4
        if not hasattr(backend, 'has_perm'):
            continue
        try:
            if backend.has_perm(user, perm, obj):
                return True
        except PermissionDenied:
            return False
    return False
	
	
C:\Python27\Lib\site-packages\django\contrib\auth\__init__.py	
def get_backends():
    return _get_backends(return_tuples=False)    ----------------------------step 5
	
	
def _get_backends(return_tuples=False):
    backends = []
    for backend_path in settings.AUTHENTICATION_BACKENDS: ------------------step 6 settings.AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) from: django\conf\global_settings.py
        backend = load_backend(backend_path)
        backends.append((backend, backend_path) if return_tuples else backend)
    if not backends:
        raise ImproperlyConfigured(
            'No authentication backends have been defined. Does '
            'AUTHENTICATION_BACKENDS contain anything?'
        )
    return backends

在文件 django\conf\global_settings.py 中可以看到 settings.AUTHENTICATION_BACKENDS = (‘django.contrib.auth.backends.ModelBackend’,) 所以分析类 ModelBackend 的源码,由上面调用关系中的 step 4 发现主要是调用类 ModelBackend 的 has_perm 方法,
经过分析,发现原来装饰器 permission_required 最后落脚到:判断装饰器的参数 myapp.add_car 是否在 对应用户所属 group 的 auth_group_permissions 和 对应用户的user_permissions 中

@permission_required('myapp.add_car') 
def myviewfunc(request):
	pass

另外发现类 ModelBackend 里的 _get_permissions 方法用到了反射,真巧妙!

Python27\Lib\site-packages\django\contrib\auth\backends.py

class ModelBackend(object):
"""
Authenticates against settings.AUTH_USER_MODEL.
"""

    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            user = UserModel._default_manager.get_by_natural_key(username)
            if user.check_password(password):
                return user
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
            UserModel().set_password(password)

    def _get_user_permissions(self, user_obj):
        return user_obj.user_permissions.all()

    def _get_group_permissions(self, user_obj):
        user_groups_field = get_user_model()._meta.get_field('groups')
        user_groups_query = 'group__%s' % user_groups_field.related_query_name()
        return Permission.objects.filter(**{user_groups_query: user_obj})

    def _get_permissions(self, user_obj, obj, from_name):
        """
        Returns the permissions of `user_obj` from `from_name`. `from_name` can
        be either "group" or "user" to return permissions from
        `_get_group_permissions` or `_get_user_permissions` respectively.
        """
        if not user_obj.is_active or user_obj.is_anonymous() or obj is not None:
            return set()

        perm_cache_name = '_%s_perm_cache' % from_name
        if not hasattr(user_obj, perm_cache_name):
            if user_obj.is_superuser:
                perms = Permission.objects.all()
            else:
                perms = getattr(self, '_get_%s_permissions' % from_name)(user_obj)  ---step 7.1---
            perms = perms.values_list('content_type__app_label', 'codename').order_by()  ---step 7.2---
            setattr(user_obj, perm_cache_name, set("%s.%s" % (ct, name) for ct, name in perms))
        return getattr(user_obj, perm_cache_name)

    def get_user_permissions(self, user_obj, obj=None):
        """
        Returns a set of permission strings the user `user_obj` has from their
        `user_permissions`.
        """
        return self._get_permissions(user_obj, obj, 'user')

    def get_group_permissions(self, user_obj, obj=None):
        """
        Returns a set of permission strings the user `user_obj` has from the
        groups they belong.
        """
        return self._get_permissions(user_obj, obj, 'group')

    def get_all_permissions(self, user_obj, obj=None):
        if not user_obj.is_active or user_obj.is_anonymous() or obj is not None:
            return set()
        if not hasattr(user_obj, '_perm_cache'):
            user_obj._perm_cache = self.get_user_permissions(user_obj)
            user_obj._perm_cache.update(self.get_group_permissions(user_obj))
        return user_obj._perm_cache

    def has_perm(self, user_obj, perm, obj=None):
        if not user_obj.is_active:
            return False
        return perm in self.get_all_permissions(user_obj, obj)
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`permission_required` 是 Django 提供的一个装饰器,用于限制用户访问某个视图函数的权限。它的参数为一个权限字符串,如果用户没有该权限,则无法访问该视图函数。 具体使用方法如下: ```python from django.contrib.auth.decorators import permission_required @permission_required('app_label.permission_name') def my_view(request): # ... ``` 其中,`app_label.permission_name` 表示权限字符串,`app_label` 是指应用的名称,`permission_name` 是指对应权限的名称。例如,如果要限制用户访问某个需要 `view_article` 权限的视图函数,可以这样使用: ```python @permission_required('myapp.view_article') def my_view(request): # ... ``` 这样,只有具有 `view_article` 权限的用户才能访问该视图函数。 另外,`permission_required` 还可以接受一个可选的参数 `login_url`,表示用户未登录时跳转的页面,默认为 `settings.LOGIN_URL`。例如: ```python @permission_required('myapp.view_article', login_url='/login/') def my_view(request): # ... ``` 这样,如果用户未登录,则会跳转到 `/login/` 页面。 示例代码: ```python from django.contrib.auth.decorators import permission_required from django.shortcuts import render @permission_required('polls.view_poll') def view_poll(request): polls = Poll.objects.all() context = {'polls': polls} return render(request, 'polls/view_poll.html', context) ``` 在这个示例中,`view_poll` 视图函数只能被具有 `polls.view_poll` 权限的用户访问。如果用户没有该权限,则会被重定向到登录页面。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值