Django权限机制实现代码详解


Posted in Python onFebruary 05, 2018

本文研究的主要是Django权限机制的相关内容,具体如下。

1. Django权限机制概述

权限机制能够约束用户行为,控制页面的显示内容,也能使API更加安全和灵活;用好权限机制,能让系统更加强大和健壮。因此,基于Django的开发,理清Django权限机制是非常必要的。

1.1 Django的权限控制

Django用user, group和permission完成了权限机制,这个权限机制是将属于model的某个permission赋予user或group,可以理解为全局的权限,即如果用户A对数据模型(model)B有可写权限,那么A能修改model B的所有实例(objects)。group的权限也是如此,如果为group C 赋予model B的可写权限,则隶属于group C 的所有用户,都可以修改model B的所有实例。

这种权限机制只能解决一些简单的应用需求,而大部分应用场景下,需要更细分的权限机制。以博客系统为例,博客系统的用户可分为『管理员』、『编辑』、『作者』和『读者』四个用户组;博客系统管理员和编辑具有查看、修改和删除所有的文章的权限,作者只能修改和删除自己写的文章,而读者则只有阅读权限。管理员、编辑和读者的权限,我们可以用全局权限做控制,而对于作者,全局权限无法满足需求,仅通过全局权限,要么允许作者编辑不属于自己的文章,要么让作者连自己的文章都无法修改。

上述的应用场景,Django自带的权限机制无法满足需求,需要引入另一种更细的权限机制:对象权限(object permission)。

Object Permission是一种对象颗粒度上的权限机制,它允许为每个具体对象授权。仍沿用最开始的例子,如果model B有三个实例 B1,B2 和B3,如果我们把B1的可写权限赋予用户A,则A可以修改B1对象,而对B2,B3无法修改。

对group也一样,如果将B2的可写权限赋予group C,则隶属于group C的所有用户均可以修改B2,但无法修改B1和B3。结合Django自带权限机制和object permission,博客系统中作者的权限控制迎刃而解:系统全局上不允许作者编辑文章,而对于属于作者的具体文章,赋予编辑权限即可。

Django其实包含了object permission的框架,但没有具体实现,object permission的实现需要借助第三方app django-guardian,我们在开发中用调用django guradian封装好的方法即可。

1.2 Django的权限项

Django用permission对象存储权限项,每个model默认都有三个permission,即add model, change model和delete model。例如,定义一个名为『Car』model,定义好Car之后,会自动创建相应的三个permission:add_car, change_car和delete_car。Django还允许自定义permission,例如,我们可以为Car创建新的权限项:drive_car, clean_car, fix_car等等

需要注意的是,permission总是与model对应的,如果一个object不是model的实例,我们无法为它创建/分配权限。

2. Django 自带权限机制的应用

2.1 Permission

如上文所述,Django定义每个model后,默认都会添加该model的add, change和delete三个permission,自定义的permission可以在我们定义model时手动添加:

class Task(models.Model):
  ...
  class Meta:
    permissions = (
      ("view_task", "Can see available tasks"),
      ("change_task_status", "Can change the status of tasks"),
      ("close_task", "Can remove a task by setting its status as closed"),
    )

每个permission都是django.contrib.auth.Permission类型的实例,该类型包含三个字段name, codename 和 content_type,其中 content_type反应了permission属于哪个model,codename如上面的view_task,代码逻辑中检查权限时要用, name是permission的描述,将permission打印到屏幕或页面时默认显示的就是name。

在model中创建自定义权限,从系统开发的角度,可理解为创建系统的内置权限,如果需求中涉及到用户使用系统时创建自定义权限,则要通过下面方法:

from myapp.models import BlogPost
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(codename='can_publish',
                    name='Can Publish Posts',
                    content_type=content_type)

2.2 User Permission管理

User对象的user_permission字段管理用户的权限:

myuser.user_permissions = [permission_list]
myuser.user_permissions.add(permission, permission, ...) #增加权限
myuser.user_permissions.remove(permission, permission, ...) #删除权限
myuser.user_permissions.clear() #清空权限

##############################################################
# 注:上面的permission为django.contrib.auth.Permission类型的实例
##############################################################

检查用户权限用has_perm()方法:

myuser.has_perm('myapp.fix_car')

has_perm()方法的参数,即permission的codename,但传递参数时需要加上model 所属app的前缀,格式为<app label>.<permission codename>

无论permission赋予user还是group,has_perm()方法均适用

附注:
user.get_all_permissions()方法列出用户的所有权限,返回值是permission name的list
user.get_group_permissions()方法列出用户所属group的权限,返回值是permission name的list

2.3 Group Permission管理

group permission管理逻辑与user permission管理一致,group中使用permissions字段做权限管理:

group.permissions = [permission_list]
group.permissions.add(permission, permission, ...)
group.permissions.remove(permission, permission, ...)
group.permissions.clear()

权限检查:

依然使用user.has_perm()方法。

2.4 permission_required 装饰器

权限能约束用户行为,当业务逻辑中涉及到权限检查时,decorator能够分离权限验证和核心的业务逻辑,使代码更简洁,逻辑更清晰。permission的decorator为permission_required

from django.contrib.auth.decorators import permission_required

@permission_required('car.drive_car')
def my_view(request):

2.5 Template中的权限检查

Template中使用全局变量perms存储当前用户的所有权限,权限检查可以参考下面例子:

{% if perms.main.add_page %}
      <li class="dropdown">
       <a href="#" rel="external nofollow" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Pages <span class="caret"></span></a>
       <ul class="dropdown-menu" role="menu">
        <li><a href="{% url 'main:admin_pages' %}" rel="external nofollow" >All Pages</a></li>
        <li><a href="{% url 'main:admin_page' %}" rel="external nofollow" >New Page</a></li>
        <li><a href="{% url 'main:admin_pages' %}?draft=true" rel="external nofollow" >Drafts</a></li>
       </ul>
      </li>
{% endif %}

3. 基于Django-guardian的object permission的应用

Django-guardian基于django的原生逻辑扩展了django的权限机制,应用django-guardian后,可以使用django-guardian提供的方法以及django的原生方法检查全局权限,django-guardian提供的object permission机制使django的权限机制更加完善。

django-guardian详细的使用文档请参考官方文档,其object permission常用方法如下:

from guardian.shortcuts import assign_perm, get_perms
from guardian.core import ObjectPermissionChecker
from guardian.decorators import permission_required

3.1 添加object permission

添加object permission使用assign_perm()方法,如为用户添加对mycar对象的drive_car权限:

assign_perm('myapp.drive_car', request.user, mycar)

assign_perm()方法也可用于group

assign_perm('myapp.drive_car', mygroup, mycar)

3.2 权限检查

3.2.1 Global permission

get_perms()方法用于检查用户的“全局权限”(global permission),与user.has_perm()异曲同工,如:

#############################
# It works! 
#############################
 if not 'main.change_post' in get_perms(request.user, post):
   raise HttpResponse('Forbidden')

#############################
# It works, too!
#############################
if not request.user.has_perm('main.change_post')
  return HttpResponse('Forbidden')

例子中虽然把post object作为参数传给get_perms()方法,但它只检查user的全局权限中是否有main.change_post权限,很多情况下可用原生的user.has_perm取代,但user和group均可作为get_perms()的传入参数,某些情况下可以使代码更简洁。

3.2.2 Object permission

Django-guardian中使用ObjectPermissionChecker检查用户的object permission,示例如下:

checker = ObjectPermissionChecker(request.user)
print checker.has_perm('main.change_post', post)

3.3 permission_required装饰器

guardian.decorators.permission_required是django-guardian权限检查的decorator,既可以检查全局权限,又可以检查对象权限(object permission),其中,accept_global_perms参数指出是否检查user的global permission,如:

from guardian.decorators import permission_required

class DeletePost(View):
  @method_decorator(permission_required('main.delete_post', 
              (models.Post, 'id', 'pk'), 
              accept_global_perms=True))
  def get(self, request, pk):
    try:
      pk = int(pk)
      cur_post = models.Post.objects.get(pk=pk)
      is_draft = cur_post.is_draft

      url = reverse('main:admin_posts')
      if is_draft:
        url = '{0}?draft=true'.format(url)  
      cur_post.delete()
    except models.Post.DoesNotExist:
      raise Http404

    return redirect(url)

注:
decorator中的(models.Post, ‘id', ‘pk')部分,用于指定object实例,如果忽略这个参数,则不论accept_global_perms值为True还是False,均仅仅检查全局权限。

4. 结论

Django原生提供了一种简单的全局权限(global permission)控制机制,但很多应用场景下,对象权限(object permission)更加有用;django-guardian是目前比较活跃的一个django extension,提供了一种有效的object permission控制机制,与django原生机制一脉相承,推荐使用。

以上就是本文关于Django权限机制实现代码详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

Python 相关文章推荐
用Python编程实现语音控制电脑
Apr 01 Python
python异常和文件处理机制详解
Jul 19 Python
Python基于sftp及rsa密匙实现远程拷贝文件的方法
Sep 21 Python
python将文本中的空格替换为换行的方法
Mar 19 Python
解决已经安装requests,却依然提示No module named requests问题
May 18 Python
解决tensorflow测试模型时NotFoundError错误的问题
Jul 27 Python
python 使用re.search()筛选后 选取部分结果的方法
Nov 28 Python
Python判断一个文件夹内哪些文件是图片的实例
Dec 07 Python
Python读取xlsx文件的实现方法
Jul 04 Python
基于django 的orm中非主键自增的实现方式
May 18 Python
如何使用pycharm连接Databricks的步骤详解
Sep 23 Python
python 爬虫爬取京东ps4售卖情况
Dec 18 Python
Django中的Signal代码详解
Feb 05 #Python
Python实现XML文件解析的示例代码
Feb 05 #Python
Python下载网络文本数据到本地内存的四种实现方法示例
Feb 05 #Python
Python实现屏幕截图的两种方式
Feb 05 #Python
Python实现连接两个无规则列表后删除重复元素并升序排序的方法
Feb 05 #Python
用python实现对比两张图片的不同
Feb 05 #Python
使用pygame模块编写贪吃蛇的实例讲解
Feb 05 #Python
You might like
php单件模式结合命令链模式使用说明
2008/09/07 PHP
php中自定义函数dump查看数组信息类似var_dump
2014/01/27 PHP
ie focus bug 解决方法
2009/09/03 Javascript
JavaScrip单线程引擎工作原理分析
2010/09/04 Javascript
web css实现整站样式互相切换
2013/10/29 Javascript
javascript实现分栏显示小技巧附图
2014/10/13 Javascript
jQuery中dequeue()方法用法实例
2014/12/29 Javascript
使用javascript实现判断当前浏览器
2015/04/14 Javascript
Jquery uploadify 多余的Get请求(404错误)的解决方法
2017/01/26 Javascript
使用selenium抓取淘宝的商品信息实例
2018/02/06 Javascript
JavaScript设计模式之构造函数模式实例教程
2018/07/02 Javascript
vue代理和跨域问题的解决
2018/07/18 Javascript
利用JavaScript缓存远程窃取Wi-Fi密码的思路详解
2018/11/05 Javascript
vue组件文档(.md)中如何自动导入示例(.vue)详解
2019/01/25 Javascript
vue 引用自定义ttf、otf、在线字体的方法
2019/05/09 Javascript
JavaScript中0、空字符串、'0'是true还是false的知识点分享
2019/09/16 Javascript
jQuery实现验证用户登录
2019/12/10 jQuery
Vue 中获取当前时间并实时刷新的实现代码
2020/05/12 Javascript
详解Webpack4多页应用打包方案
2020/07/16 Javascript
vscode 使用Prettier插件格式化配置使用代码详解
2020/08/10 Javascript
微信小程序轮播图swiper代码详解
2020/12/01 Javascript
Python2.5/2.6实用教程 入门基础篇
2009/11/29 Python
Pyramid Mako模板引入helper对象的步骤方法
2013/11/27 Python
一个基于flask的web应用诞生 flask和mysql相连(4)
2017/04/11 Python
基于Django的python验证码(实例讲解)
2017/10/23 Python
pycharm激活码快速激活及使用步骤
2020/03/12 Python
python如何导入依赖包
2020/07/13 Python
美国创意之家:BulbHead
2017/07/12 全球购物
酒店开业策划方案
2014/06/02 职场文书
1000字打架检讨书
2014/11/03 职场文书
运动会加油稿30字
2015/07/21 职场文书
民间借贷纠纷起诉书
2015/08/03 职场文书
基于python制作简易版学生信息管理系统
2021/04/20 Python
Win11 Build 22000.51版本文件资源管理器“命令栏”和上下文菜单有什么新变化?
2021/11/21 数码科技
【海涛解说】pis亲自推荐,其实你从来不会玩NW
2022/04/01 DOTA
Innodb存储引擎中的后台线程详解
2022/04/03 MySQL