django权限管理
Django权限管理
权限是能够约束用户行为和控制页面显示内容的一种机制。一个完整的权限应该包含用户,对象和权限3个要素,即什么用户对什么对象有什么样的权限。
Django自带的权限机制仅针对模型,即一个用户如果对Article模型有change权限,那么该用户对所有文章都有修改权限。如果实现对单个对象的权限管理,可以借助于第三方库guardian。本文将从分别从自带的“auth模块”权限机制和guardian展开介绍。
一、 自带权限机制
auth模块是Django提供的标准权限管理系统,可以提供用户身份认证, 用户组和权限管理。
1.1表结构
与 auth 模块有关的数据库表有 6 张,分别是
项目 |
作用 |
备注 |
auth_user. |
用户信息 |
|
auth_group |
组信息 |
每个组拥有id和name两个字段 |
auth_user_groups |
user和group之间的关系 |
|
auth_user_user_permissions |
user和prmission之间的关系 |
|
auth_permission |
权限 |
每条权限拥有id ,name , content_type_id, codename四个字段 |
auth_group_permissions |
用户组和权限的对应关系 |
使用用户组管理权限是一个更方便的方法,Group中包含多对多字段permissions |
1.2相关操作
1.2.1 用户操作
创建用户
from django.contrib.auth.models import User
user = User.objects.create_user(username, email, password)
user.save()
用户认证
from django.contrib.auth import authenticate
user = authenticate(username=username, password=password)
认证用户的密码是否有效, 若有效则返回代表该用户的user对象, 若无效则返回None。
修改用户密码
user = auth.authenticate(username=username, password=old_password)
if user is not None:
user.set_password(new_password)
user.save()
登录
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
退出登录
from django.contrib.auth import logout
def logout_view(request):
logout(request)
1.2.2 组操作
django.contrib.auth.models.Group定义了用户组的模型, 每个用户组拥有id和name两个字段,该模型在数据库被映射为auth_group数据表。User对象中有一个名为groups的多对多字段,多对多关系由auth_user_groups数据表维护。Group对象可以通过user_set反向查询用户组中的用户。
创建组
group = Group.objects.create(name=group_name)
group.save()
删除组
group.delete()
用户加入用户组
user.groups.add(group)
#或者
group.user_set.add(user)
用户退出用户组
user.groups.remove(group)
#或者
group.user_set.remove(user)
用户退出所有用户组
user.groups.clear()
用户组中所有用户退出组
group.user_set.clear()
1.2.3 权限操作
检查用户权限
user.has_perm方法用于检查用户是否拥有操作某个模型的权限,若拥有权限则返回True。
user.has_perm('blog.add_article')
user.has_perm('blog.change_article')
user.has_perm('blog.delete_article')
抛出异常的权限检查
has_perm仅是进行权限检查, 即是用户没有权限它也不会阻止程序员执行相关操作。
@permission_required装饰器可以代替has_perm并在用户没有相应权限时重定向到登录页或者抛出异常。
# permission_required(perm[, login_url=None, raise_exception=False])
@permission_required('blog.add_article')
def post_article(request):
pass
每个模型默认拥有增(add), 改(change), 删(delete)权限。
添加用户权限
user.user_permissions.add(permission)
删除用户权限:
user.user_permissions.delete(permission)
清空用户权限:
user.user_permissions.clear()
用户拥有他所在用户组的权限, 使用用户组管理权限是一个更方便的方法。Group中包含多对多字段permissions, 在数据库中由auth_group_permissions数据表维护。
添加组权限:
group.permissions.add(permission)
删除组权限:
group.permissions.delete(permission)
清空组权限:
group.permissions.clear()
某个用户所在用户组的权限
user_A.get_group_permissions()
某个用户的所有权限
user_A.get_all_permissions()
自定义权限
在定义Model时可以使用Meta自定义权限:
class Discussion(models.Model):
...
class Meta:
permissions = (
("create_discussion", "Can create a discussion"),
("reply_discussion", "Can reply discussion"),
)
判断用户是否拥有自定义权限:
user.has_perm('blog.create_discussion')
二、 guardian对象级别的权限控制
2.1 表结构
guardian_groupobjectpermission |
组与model以及model内的具体object的权限对应关系 |
|
guardian_usrerobjectpermission |
用户与model以及model内的具体object的权限对应关系 |
|
以guardian_groupobjectpermission表为例,说下各字段的含义
id:默认主键
- object_pk:object的id,标识具体是哪个对象需要授权,对应的是具体的某一条数据
content_type_id:记录具体哪个表的id,对应的是django系统表django_content_type内的某条数据,django所有注册的model都会在这个表里记录
group_id:记录是那个组的用户会有权限,对应的是auth_group表里的某条记录
permission_id:记录具体的某个权限,对应的是auth_permission表里的某条记录
2.2 相关操作
2.2.1 权限分配
guardian.shortcuts.assign(perm, user_or_group, obj=None)
这个方法接受3个参数:perm,这个参数是一个字符串,代表一个许可,格式必须为<app>.<perm_codename>或者<perm_codename>。但是如果第三个参数是None,则必须为<app>.<perm_codename>格式。因此建议还是统一使用<app>.<perm_codename>格式。注意app并不是app的全路径,而是最后一级的模块名。user_or_group,参数是一个User或Group类型的对象。obj,这个参数就是相关的对象了。改参数是可省略的,如果省略则赋予Model权限。
赋予模型级别的权限
from guardian.shortcuts import assign
user = User.objects.create(username='liuyong')
assign('app.view_task', user)
user.has_perm('app.view_task') >>True
赋予对象级别的权限
user = User.objects.get(username='liuyong')
user.user_permissions.clear()
task = Task.objects.create(summary='Some job', content='')
assign('app.view_task', user, task)
设置group权限
>>> group = Group.objects.create(name='employees')
>>> assign('change_task', group, task)
删除用户权限
guardian.shortcuts.remove_perm(perm,user_or_group=None, obj=None)
样例代码:
>>> from guardian.shortcuts import remove_perm
>>> remove_perm('change_site', user, site)
>>> user = User.objects.get(username='joe') #刷新user对象缓存
>>> user.has_perm('change_site', site)
False
2.2.2 其他函数
除了Django的user.has_perm方法之外,guardian提供了一些帮助函数。
根据用户和对象,获取权限:
guardian.shortcuts.get_perms(user_or_group,obj)
例如:'permcodename' in get_perms(group,obj)来判断该组是否有这个权限,因为group没有has_perm方法。
根据用户和权限获取对象
guardian.shortcuts.get_objects_for_user(user, perms, klass=None, use_groups=True, any_perm=False)
例如:get_objects_for_user(user,'app.change_post')
>>所有可编辑的帖子
第二个参数也可以写成列表(同时满足):
>>> get_objects_for_user(user, ['engine.commontask_run', 'engine.commontask_change'])
<QuerySet [<CommonTask: Dev-Coffee-Web-发布>]>
想要仅满足列表中的任意一个权限,可以添加第三个参数any_perm=True
>>> get_objects_for_user(user, ['engine.commontask_run', 'engine.commontask_change'], any_perm=True)
<QuerySet [<CommonTask: Dev-Coffee-Web-发布>, <CommonTask: Qa-Coffee-Web-发布>, <CommonTask: dev-ops-coffee-build>]>
获取对象的所有权限:
>>> from guardian.shortcuts import get_users_with_perms
>>> get_users_with_perms(task)
<QuerySet [<User: dev@ops-coffee.cn>, <User: qa@ops-coffee.cn>, <User: test@ops-coffee.cn>]>
可以让超级用户也在其中:
get_users_with_perms(task, with_superusers=True)
返回的结构是以用户为key权限为value的一个字典,看起来清晰明
>>> get_users_with_perms(task, with_superusers=True, attach_perms=True)
如果我们仅想查看具有某个权限的用户,可以设置only_with_perms_in参数
>>>get_users_with_perms(task, with_superusers=True, only_with_perms_in=['commontask_change'])
<QuerySet [<User: admin@163.com>, <User: qa@ops-coffee.cn>]>
只想查看直接赋予用户的权限,而并非间接通过group取得的权限用户列表,我们可以设置参数with_group_users=False,此参数默认为True
>>> get_users_with_perms(task, with_superusers=True, with_group_users=False)
<QuerySet [<User: admin@163.com>, <User: dev@ops-coffee.cn>, <User: qa@ops-coffee.cn>]>
准备模型和自定义权限
from django.db import models
from django.contrib.auth.models import User
class Task(models.Model):
summary = models.CharField(max_length=32)
content = models.TextField()
reported_by = models.ForeignKey(User)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
permissions = (
('view_task', 'View task'),
)
说明:permissions使我们自定义的权限。默认情况下Django为每个模型注册4个权限
view_模型名
add_模型名
change_模型名
delete_模型名
判断权限的包装器
guadian.core.ObjectPermissionChecker
主要有has_perm(perm,obj)和get_perms(obj)两个方法。并且提供缓存机制,在多次查找权限的时候,可以使用它。
>>>
epser = User.objects.get(username='esper')
>>> site = Site.objects.get_current()
>>> from guardian.core import ObjectPermissionChecker
>>> checker = ObjectPermissionChecker(esper) # 我们也可以传入组group对象
>>> checker.has_perm('change_site', site)
True
>>> checker.has_perm('add_site', site) # 这次将不会产生数据库查询
False
>>> checker.get_perms(site)
[u'change_site']
2.2.3 孤儿对象许可
所谓孤儿许可,大多数情况下,可能没事,但一旦发生,后果有可能非常严重。比方说,有一个对象A。我们通过权限设置,设定joe用户对该对象有着编辑权限。忽然有一天,用户joe被删除了。可想而知,我们分配而产生的UserObjectPermission对象仍然在数据库里面,记录着:joe 有对A的编辑权限。又有一天,一个用户注册了一个用户,用户username为joe。因为之前的那个纪录,joe用户拥有对A的编辑权限。而此joe非彼joe,我们犯了一个大错误!
因此,当删除User和相关Object时,一定要删除其相关的所有UserObjectPermission和GroupObjectPermission对象。要解决这个问题有三种,一是显式编码,二是通过其提供的自定义django命令:$ python manage.py clean_orphan_obj_perms,三是定期调用guardian.utils.clean_orphan_obj_perms(),该函数会返回删除的对象数目。也可使用celery定期调度任务。
参考文献:
https://blog.csdn.net/weixin_42134789/article/details/84567337
官网:https://django-guardian.readthedocs.io/en/stable/userguide/remove.html