1. User Model
Django内置的权限系统包括以下三个部分:
- 用户(Users)
- 许可(Permissions):用来定义一个用户(user)是否能够做某项任务(task)
- 组(Groups):一种可以批量分配许可到多个用户的通用方式
首先需要在Django中安装这个组件:
- 将’django.contrib.auth’和’django.contrib.contenttypes’放到settings.py中的INSTALLED_APPS中。(使用contenttypes的原因是auth中的Permission模型依赖于contenttypes)
- 执行数据库迁移
‘django.contrib.auth.models’里User模型有两个多对多的属性:groups和user_permissions
【例】es = User.objects.create_user(‘esperyong’,’esperyong@gmail.com’,’123456’)
1.直接将一个列表赋值给该属性:
es.groups = [group_list]
es.user_permissions = [permission_list]
2.使用add方法将对象加入:
es.groups.add(group, group, …)
es.user_permissions.add(permission, permission, …)
3.使用remove方法将对象删除:
es.groups.remove(group, group, …)
es.user_permissions.remove(permission, permission, …)
4.使用clear方法将所有对象删除:
es.groups.clear()
es.user_permissions.clear()
User对象有以下几个属性:
- username:字符串类型。必填。30个字符以内。
- first_name:字符串类型。可选。30个字符以内。
- last_name:字符串类型。可选。30个字符以内。
- email:可选。
- password:明文密码的hash或者是某种元数据。
is_staff
:Boolean类型。用这个来判断是否用户可以登录进入admin site。is_active
:Boolean类型。用来判断该用户是否是可用激活状态。在删除一个帐户的时候,可以选择将这个属性置为False,而不是真正删除。这样如果应用有外键引用到这个用户,外键就不会被破坏。is_superuser
:Boolean类型。该属性用来表示该用户拥有所有的许可,而无需明确的赋予给他。last_login
:datetime类型。最近一次登陆时间。- date_joined:datetime类型。创建时间。
除了DjangoModel对象的通用方法之外,User对象有以下特有方法:
- is_anonymous():
永远返回False.用来将User对象和AnonymousUser(未登录的匿名用户)对象作区分用的识别方法。通常,最好用is_authenticated()方法。 is_authenticated()
:
永远返回True。该方法不代表该用户有任何的许可,也不代表该用户是active的,而只是表明该用户提供了正确的username和password。get_full_name()
:
返回一个字符串,是first_name和last_name中间加一个空格组成。set_password(raw_password)
:
调用该方法时候传入一个明文密码,该方法会进行hash转换。该方法调用之后并不会保存User对象。check_password(raw_password)
:
如果传入的明文密码是正确的返回True。该方法和set_password是一对,也会考虑hash转换。- set_unusable_password():
将用户设置为没有密码的状态。调用该方法后,check_password()方法将会永远返回false。但是如果,调用set_password()方法重新设置密码后,该方法将会失效,has_usable_password()也会返回True。 - has_usable_password():
在调用set_unusable_password()方法之后,该方法返回False,正常情况下返回True。 get_group_permissions(obj=None)
:
返回该用户通过组所拥有的许可(字符串列表每一个代表一个许可)。obj如果指定,将会返回关于该对象的许可,而不是模型。get_all_permissions(obj=None)
:
返回该用户所拥有的所有的许可,包括通过组的和通过用户赋予的许可。has_perm(perm,obj=None)
:
如果用户有传入的perm,则返回True。perm可以是一个格式为:’. ‘的字符串。如果User对象为inactive,该方法永远返回False。和前面一样,如果传入obj,则判断该用户对于这个对象是否有这个许可。 - has_perms(perm_list,obj=None):
和has_perm一样,不同的地方是第一个参数是一个perm列表,只有用户拥有传入的每一个perm,返回值才是True。 - has_module_perms(package_name):
传入的是Django app label,按照’. ‘格式。当用户拥有该app label下面所有的perm时,返回值为True。如果用户为inactive,返回值永远为False。 email_user(subject,message,from_email=None)
:
发送一封邮件给这个用户,依靠的是该用户的email属性。如果from_email不提供的话,Django会使用settings中的DEFAULT_FROM_EMAIL发送。- get_profile():
返回一个和Site相关的profile对象,用来存储额外的用户信息。
User对象的UserManager:
- create_user(username,email=None,password=None)
- make_random_password(length=10,allowed_chars=’abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789’):该方法返回一个给定长度和允许字符集的密码。其中默认的allowed_chars有一些字符没有,比如i,l等等。
2. User Profile
使用UserProfile存储用户的额外信息 :1
2
3
4
5
6
7
8class UserProfile(models.Model):
# 和User的一对一关系属性,该属性必填.
user = models.OneToOneField(User)
# 其他需要存储的属性
# User因为是Django提供的,如果想要在其上增加一些自己需要的字段和方法,不太好加入,因此UserProfile是达成这个目标的一个有利工具
accepted_eula = models.BooleanField()
favorite_animal = models.CharField(max_length=20, default="Dragons.")
在settings中声明一个变量
AUTH_PROFILE_MODULE = ‘accounts.UserProfile’
这样,User对象的get_profile()方法就会返回这个对象了。(UserProfile对象不会和User一起自动创建),因此要注册一个handler到User的post_save signal。1
2
3
4
5
6
7
8
9
10from django.db.models.signals import post_save
# 定义了UserProfile
# ...
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
3. Login Logout
Login
需要两个函数:authenticate(username,password)
和login(request,user)
,位于django.contrib.auth模块中
authenticate(usernae,password)函数需要两个参数username,password,如果校验通过则返回User对象,如果校验不通过返回None。
login接受两个参数,第一个是request对象,第二个是user对象。login方法使用SessionMiddleware将userID存入session当中。
1
2
3
4user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
check_password(password,encoded):第一个参数是明文密码,第二个是加密过的密码。
make_password(password[,salt,hashers]):根据给定的明文密码,salt,和Django支持的加密算法,返回一个加密的密码。
is_password_unable(encoded_password):判断是否给定字符串是一个hashed密码,有机会通过check_password()函数的校验。
上述三个方法位于模块django.contrib.auth.hashers:
Logout
login(request)
signals
django.contrib.auth.signals.user_logged_in
django.contrib.auth.signals.user_logged_out
三个参数:
- sender:user的class,如果是logout事件该值有可能是None如果用户根本就没有验证通过。
- request:HttpRequest对象
- user:user对象,如果是logout事件该值有可能是None如果用户根本就没有验证通过。
decorator
django.contrib.auth.decorators.login_required([redirect_field_name=REDIRECT_FIELD_NAME,login_url=None])
两个参数:
- redirect_field_name:默认值是next。用来定义登陆成功之后的跳回之前访问界面的url。
- login_url:默认值是settings.LOGIN_URL。用来指定登陆界面的url。如果不传入改参数,就需要确保settings.LOGIN_URL的值是正确设置的。
4. 许可(Permission) 和 用户组(Group)
许可(Permissions)
django 会为每个用户给新出现的Model增加add,change,delete这三个权限。可以用任何一个user对象执行user.hash_perm(‘school.add_studygroup’)。——(add/change/delete,app:school,model:StudyGroup),都会返回True。
自定义一些许可
:
- 在Model类的meta属性中添加permissions定义。
1
2
3
4
5
6
7
8class Discussion(models.Model):
...
class Meta:
permissions = (
("open_discussion", "Can create a discussion"),
("reply_discussion", "Can reply discussion"),
("close_discussion", "Can remove a discussion by setting its status as closed"),
)
模型类:Discussion。可以创建几个权限来对这个模型的权限许可进行控制,控制某些人可以发起讨论、发起回复,关闭讨论。
- 执行数据库迁移,数据库中就有这三个许可了。
- 之后可以将上面的权限赋予给用户,方法两种:
(1).通过某一个user的user_permissions属性:
user.user_permissions.add(permission, permission, …)
(2).通过user的一个组,然后通过Group的permissions属性:
group.permissions.add(permission, permission, …)
如果要判断一个用户是否有发表讨论的权限:user.has_perm(‘school.open_discussion’)
Permission 属性
所属模块:django.contrib.auth.models
属性:
- name:必填。小于50个字符。例如:’Can publish’(描述)。
- content_type:必填。一个指向django_content_type数据库表,对于每一个Django模型,在这个表里面都有一个记录对应。
- codename:必填。小于100个字符。例如:’can_publish’。
编程创建权限
:1
2
3
4
5
6
7from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
content_type = ContentType.objects.get(app_label='school', model='Discussion')
permission = Permission.objects.create(codename='can_publish',
name='Can Publish Discussions',
content_type=content_type)
在界面中使用许可
:
在模板代码中,有两个属性,是django给你提供好的,一个是user,一个是perms。
user:
user变量是一个User或者AnoymousUser对象。1
2
3
4
5{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}. Thanks for logging in.</p>
{% else %}
<p>Welcome, new user. Please log in.</p>
{% endif %}perms:
perms变量是一个django.contrib.auth.context_processors.PermWrapper对象,对当前用户的User.has_module_perms和User.has_perm进行了封装。
比如判断当前用户是否拥有school应用下的所有权限,则使用
判断当前用户是否拥有school应用下发表讨论的权限,则使用1
2
3
4
5
6
7
8
9
10
11{% if perms.school %}
<p>You have permission to do something in the school app.</p>
{% if perms.school.publish_discussion %}
<p>You can discussion!</p>
{% endif %}
{% if perms.school.reply_discussion %}
<p>You can reply discussion!</p>
{% endif %}
{% else %}
<p>You don't have permission to do anything in the school app.</p>
{% endif %}
实现: settings 中的TEMPLATE_CONTEXT_PROCESSORS中定义的django.contrib.auth.context_processors.auth处理器。在django进入解析Template之前,首相要经过这一个个的context_processor。
用户组(Group)
其作用是在权限控制中可以批量对用户的许可进行分配。
将一个以后加入到一个Gruop中,该用户就拥有了所有该Group所分配的所有许可。
Group 属性
- name:必填。少于80个字符。
- permissions:多对多引用。和user的user_permissions属性类型相同。我们可以通过下面代码给该组赋予权限。
1
2
3
4group.permissions = [permission_list]
group.permissions.add(permission, permission, ...)
group.permissions.remove(permission, permission, ...)
group.permissions.clear()
5. Authentication backends
django实现的这套permission体系,在底层被抽象为authentication backends。实际开发中可能需要对已有的权限系统进行扩展。
django中,所有的authentication backends,可以配置settings中一个 变量AUTHENTICATION_BACKENDS来做到,默认的设置:AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
校验:
user:authenticate(self,username=None,password=None) 或者authenticate(self,token=None),get_user(self,user_id)
permission:get_group_permissions,get_all_permission,has_perm,has_module_perms
6. Django实现Object级别的权限控制-django-guardian
安装配置django-guardian:
- pip install django-guardian
在INSTALLED_APPS变量中加入guardian:
1
2
3INSTALLED_APPS = (
'guardian',
)添加一个backend到AUTHENTICATION_BACKENDS中,这样django才会具有对对象的权限控制:
1
2
3
4AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # django默认的backend
'guardian.backends.ObjectPermissionBackend',
)
在guardian中,还支持对匿名用户AnoymousUser的Object级别的权限控制。(比如说允许匿名用户发言的论坛或者blog)这需要在settings中加入:
ANONYMOUS_USER_ID=-1
之后执行数据库迁移,系统将会创建一个User实例,叫做AnonymouseUser
设置和使用对象权限
guardian.shortcuts.assign(perm, user_or_group, obj=None)
,这个方法接受3个参数:
- perm
这个参数是一个字符串,代表一个许可,格式为<app>.<perm_codename>
或者<perm_codename>
格式,但如果第三个参数为None,则必须为前者格式。 - user_or_group
这个参数是一个User或者Group类型的对象。 - obj
这个参数就是相关的对象。可省略,若省略则赋予Model权限。
【实例】
赋予模型级别的权限:1
2
3
4from guardian.shortcuts import assign
user = User.objects.create(username='zhangsan')
assign('app.view_task',user)
user.has_perm('app.view_task') >>>True
注意:一旦赋予模型级别的权限,那么所有该模型的对象级别的权限就都有了。
赋予对象级别的权限:1
2
3
4
5task = Task.objects.create(summary='Some job', content='')
assign('app.view_task', user, task)
user = User.objects.get(username='liuyong')#刷新缓存
user.has_perm('app.view_task',task) >>>True
user.has_perm('app.view_task') >>>False #模型级别的权限还没有
通过设置group来使用户具有相应的权限:
group = Group.objects.create(name=’employees’)
assign(‘change_task’, group, task)
user.groups.add(group)
user.has_perm(‘change_task’, task) >>>True
删除某个用户对某个对象的某种许可,这个函数的参数和assign相同。guardian.shortcuts.remove_perm(perm,user_or_group=None, obj=None)
1
2
3
4from 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
Guardian在View中的使用guardian.shortcuts.get_perms(user_or_group,obj)
:返回user/group对象对obj对象所有的权限。guardian.shortcuts.get_objects_for_user(user,perms,klass=None,use_groups=True,any_perm=False)
:获得该用户下指定perm列表中的所有对象。1
get_objects_for_user(user,'app.change_post')# 返回可编辑帖子
guadian.core.ObjectPermissionChecker
:该方法是一个用来判断权限的包装器,针对user和group提供权限相关的访问方法,主要有has_perm(perm,obj)和get_perms(obj)两个方法。并且提供缓存机制,在多次查找权限的时候可以使用它。1
2
3
4
5
6
7
8
9
10>>> 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']
【add】site = Site.objects.get_current()
等效于 current_site = Site.objects.get(id=settings.SITE_ID)
site模型管理器具备一个get_current()方法获得当前的站点域
使用view的decorator
1 | >>> joe = User.objects.get(username='joe') |
只有拥有对name=foobars的Group对象拥有auth.change_group权限的用户,才能够执行这个view函数,否则返回的将是状态码为403的Response对象。1
2
3
4>>> request.user = joe
>>> joe.groups.add(foobars)
>>> edit_group(request, group_name='foobars')
<django.http.HttpResponse object at 0x102b8c8d0> # view方法得以顺利访问了
Guardian在模版中的使用
标签:get_obj_perms
需要加载guardian_tags
标签库 :1
{% load guardian_tags %}
标签格式为:1
{% get_obj_perms user/group for obj as "context_var" %}
例子代码如下:1
2
3
4{% get_obj_perms request.user for flatpage as "flatpage_perms" %}
{% if "delete_flatpage" in flatpage_perms %}
<a href="/pages/delete?target={{ flatpage.url }}">Remove page</a>
{% endif %}
孤儿对象许可
当我们删除User和相关的Object的时候,我们一定要删除其相关的所有UserObjectPermission和GroupObjectPermission对象。
三个处理方法:
- 显式编码。
通过其提供的自定义django命令。
1
2$ python manage.py clean_orphan_obj_perms
Removed 11 object permission entries with no targets定期调用guardian.utils.clean_orphan_obj_perms(),该函数会返回删除的对象数目,在python中,可以使用celery定期调度这个任务。
最合理的解决办法,手动编码实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.db.models.signals import pre_delete
from guardian.models import UserObjectPermission
from guardian.models import GroupObjectPermission
from school.models import StudyGroup
def remove_obj_perms_connected_with_user(sender, instance, **kwargs):
filters = Q(content_type=ContentType.objects.get_for_model(instance),
object_pk=instance.pk)
UserObjectPermission.objects.filter(filters).delete()
GroupObjectPermission.objects.filter(filters).delete()
pre_delete.connect(remove_obj_perms_connected_with_user, sender=StudyGroup)