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数据库操作常用功能使用详解(创建表/插入数据/获取数据)
Dec 06 Python
实例解析Python的Twisted框架中Deferred对象的用法
May 25 Python
使用python编写监听端
Apr 12 Python
python语音识别实践之百度语音API
Aug 30 Python
解决在pycharm运行代码,调用CMD窗口的命令运行显示乱码问题
Aug 23 Python
django创建简单的页面响应实例教程
Sep 06 Python
python selenium实现发送带附件的邮件代码实例
Dec 10 Python
python构造函数init实例方法解析
Jan 19 Python
python实现从尾到头打印单链表操作示例
Feb 22 Python
python 线性回归分析模型检验标准--拟合优度详解
Feb 24 Python
matlab中二维插值函数interp2的使用详解
Apr 22 Python
Python爬虫如何应对Cloudflare邮箱加密
Jun 24 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
在PHP3中实现SESSION的功能(一)
2006/10/09 PHP
需要发散思维学习PHP
2009/06/29 PHP
php的list()的一步操作给一组变量进行赋值的使用
2011/05/18 PHP
php中随机函数mt_rand()与rand()性能对比分析
2014/12/01 PHP
yii2整合百度编辑器umeditor及umeditor图片上传问题的解决办法
2016/04/20 PHP
Smarty变量用法详解
2016/05/11 PHP
LAMP环境使用Composer安装Laravel的方法
2017/03/25 PHP
PHP面向对象之里氏替换原则简单示例
2018/04/08 PHP
PHP创建文件及写入数据(覆盖写入,追加写入)的方法详解
2019/02/15 PHP
解决Laravel 不能创建 migration 的问题
2019/10/09 PHP
php封装的page分页类完整实例代码
2020/02/01 PHP
JQuery实现自定义对话框的代码
2008/06/15 Javascript
扩展easyui.datagrid,添加数据loading遮罩效果代码
2010/11/02 Javascript
Knockout数组(observable)使用详解示例
2013/11/15 Javascript
node.js中的fs.createReadStream方法使用说明
2014/12/17 Javascript
jquery使整个div区域可以点击的方法
2015/06/24 Javascript
jQuery打字效果实现方法(附demo源码下载)
2015/12/18 Javascript
基于jquery实现智能表单验证操作
2016/05/09 Javascript
js实现tab选项卡切换功能
2017/01/13 Javascript
C#微信小程序服务端获取用户解密信息实例代码
2017/03/10 Javascript
bootstrap手风琴折叠示例代码分享
2017/05/22 Javascript
angularjs下拉框空白的解决办法
2017/06/20 Javascript
js实现本地时间同步功能
2017/08/26 Javascript
C#实现将一个字符转换为整数
2017/12/12 Javascript
Vue js 的生命周期(看了就懂)(推荐)
2019/03/29 Javascript
Python函数可变参数定义及其参数传递方式实例详解
2015/05/25 Python
浅谈python爬虫使用Selenium模拟浏览器行为
2018/02/23 Python
Python设置在shell脚本中自动补全功能的方法
2018/06/25 Python
Python走楼梯问题解决方法示例
2018/07/25 Python
在Python中实现shuffle给列表洗牌
2018/11/08 Python
Python 中Django安装和使用教程详解
2019/07/03 Python
一款纯css3实现的tab选项卡的实列教程
2014/12/11 HTML / CSS
公司感谢信范文
2015/01/22 职场文书
新闻稿怎么写
2015/07/18 职场文书
MySQL数据库压缩版本安装与配置详细教程
2021/05/21 MySQL
jdbc中自带MySQL 连接池实践示例
2022/07/23 MySQL