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异常处理慎用“except:pass”建议
Apr 02 Python
Python UnicodeEncodeError: 'gbk' codec can't encode character 解决方法
Apr 24 Python
Android分包MultiDex策略详解
Oct 30 Python
在python中以相同顺序shuffle两个list的方法
Dec 13 Python
用Python和WordCloud绘制词云的实现方法(内附让字体清晰的秘笈)
Jan 08 Python
python实现五子棋小程序
Jun 18 Python
Python Celery多队列配置代码实例
Nov 22 Python
利用setuptools打包python程序的方法步骤
Jan 18 Python
利用PyTorch实现VGG16教程
Jun 24 Python
PyCharm+PyQt5+QtDesigner配置详解
Aug 12 Python
Python实现像awk一样分割字符串
Sep 15 Python
python 实现socket服务端并发的四种方式
Dec 14 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读取文件内容的几种方法详解
2013/06/26 PHP
php计算当前程序执行时间示例
2014/04/24 PHP
thinkphp中memcache的用法实例
2014/11/29 PHP
详谈PHP中public,private,protected,abstract等关键字的用法
2017/12/31 PHP
js实现权限树的更新权限时的全选全消功能
2009/02/17 Javascript
ASP.NET中AJAX 调用实例代码
2012/05/03 Javascript
js/jQuery对象互转(快速操作dom元素)
2013/02/04 Javascript
jquery鼠标滑过提示title具体实现代码
2013/08/06 Javascript
JS this作用域以及GET传输值过长的问题解决方法
2013/08/06 Javascript
javascript弹出拖动窗口
2015/08/11 Javascript
js控制多图左右滚动切换效果代码分享
2015/08/26 Javascript
jQuery基于BootStrap样式实现无限极地区联动
2016/08/26 Javascript
微信公众号支付H5调用支付解析
2016/11/04 Javascript
Bootstrap模态框插入视频的实现代码
2017/06/25 Javascript
基于js粘贴事件paste简单解析以及遇到的坑
2017/09/07 Javascript
webpack4的迁移的使用方法
2018/05/25 Javascript
详解vue中axios的封装
2018/07/18 Javascript
JavaScript学习笔记之数组基本操作示例
2019/01/09 Javascript
js实现录音上传功能
2019/11/22 Javascript
详解阿里Node.js技术文档之process模块学习指南
2021/01/04 Javascript
[02:05]2014DOTA2西雅图邀请赛 专访啸天mik夫妻档
2014/07/08 DOTA
详解Python中的Descriptor描述符类
2016/06/14 Python
把csv文件转化为数组及数组的切片方法
2018/07/04 Python
PyTorch基本数据类型(一)
2019/05/22 Python
Pycharm 如何一键加引号的方法步骤
2021/02/05 Python
css3实现波纹特效、H5实现动态波浪效果
2018/01/31 HTML / CSS
Html5实现如何在两个div元素之间拖放图像
2013/03/29 HTML / CSS
详解HTML5布局和HTML5标签
2020/10/26 HTML / CSS
华丽的手绘陶瓷:MacKenzie-Childs
2017/02/04 全球购物
Nobody Denim官网:购买高级女士牛仔裤
2021/03/15 全球购物
如何选择使用结构还是类
2014/05/30 面试题
财务总监岗位职责
2014/03/07 职场文书
贷款收入证明范本
2015/06/12 职场文书
SQL Server中交叉联接的用法详解
2021/04/22 SQL Server
Nginx四层负载均衡的配置指南
2021/06/11 Servers
TV动画《神废柴☆偶像》公布先导PV
2022/03/20 日漫