Django 内置权限扩展案例详解


Posted in Python onMarch 04, 2019

当Django的内置权限无法满足需求的时候就自己扩展吧~

背景介绍

overmind项目使用了Django内置的权限系统,Django内置权限系统基于model层做控制,新的model创建后会默认新建三个权限,分别为:add、change、delete,如果给用户或组赋予delete的权限,那么用户将可以删除这个model下的所有数据。

原本overmind只管理了我们自己部门的数据库,权限设置只针对具体的功能不针对细粒度的数据库实例,例如用户A 有审核的权限,那么用户A 可以审核所有的DB,此时使用内置的权限系统就可以满足需求了,但随着系统的不断完善要接入其他部门的数据库管理,这就要求针对不同用户开放不同DB的权限了,例如A部门的用户只能操作A部门的DB,Django内置基于model的权限无法满足需求了。

实现过程

先来确定下需求:

1.  保持原本的基于功能的权限控制不变,例如用户A有查询权限,B有审核权限

2.  增加针对DB实例的权限控制,例如用户A只能查询特定的DB,B只能审核特定的DB

对于上边需求1用内置的权限系统已经可以实现,这里不赘述,重点看下需求2,DB信息都存放在同一个表里,不同用户能操作不同的DB,也就是需要把每一条DB信息与有权限操作的用户进行关联,为了方便操作,我们考虑把DB跟用户组关联,在用户组里的用户都有权限,而操作类型经过分析主要有两类读和写,那么需要给每个MySQL实例添加两个字段分别记录对此实例有读和写权限的用户组

如下代码在原来的model基础上添加 read_groupswrite_groups 字段,DB实例跟用户组应是ManyToManyField多对多关系,一个实例可以关联多个用户组,一个用户组也可以属于多个实例

class Mysql(models.Model):
 Env = (
 (1, 'Dev'),
 (2, 'Qa'),
 (3, 'Prod'),
 )
 create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
 update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')

 project_id = models.IntegerField(verbose_name='项目')
 project_tmp = models.CharField(max_length=128, default='')
 environment = models.IntegerField(choices=Env, verbose_name='环境')

 master_host = models.GenericIPAddressField(verbose_name='master主机')
 master_port = models.IntegerField(default=3306, verbose_name='master端口')

 slave_host = models.GenericIPAddressField(null=True, verbose_name='slave主机')
 slave_port = models.IntegerField(null=True, default=3306, verbose_name='slave端口')

 database = models.CharField(max_length=64, verbose_name='数据库')

 read_groups = models.ManyToManyField(Group, related_name='read', verbose_name='读权限')
 write_groups = models.ManyToManyField(Group, related_name='write', verbose_name='写权限')

 description = models.TextField(null=True, verbose_name='备注')

model确定了,接下来我们分三部分详细介绍下权限验证的具体实现

列表页权限控制

Django 内置权限扩展案例详解

如上图列表页,每个用户进入系统后只能查看自己有读权限的MySQL实例列表,管理员能查看所有,代码如下:

def mysql(request):
 if request.method == 'GET':
 if request.user.is_superuser:
 _lists = Mysql.objects.all().order_by('id')
 else:
 # 获取登录用户的所有组
 _user_groups = request.user.groups.all()

 # 构造一个空的QuerySet然后合并
 _lists = Mysql.objects.none()
 for group in _user_groups:
 _lists = _lists | group.read.all()

 return render(request, 'overmind/mysql.index.html', {'request': request, 'lPage': _lists})

实现的思路是:获取登录用户的所有组,然后循环查询每个组有读取权限的数据库实例,最后把每个组有权限读的数据库实例进行合并返回

获取登录用户的所有组用到了ManyToMany的查询方法: request.user.groups.all()

最终返回的一个结果是QuerySet,所以我们需要先构造一个空的Queryset: Mysql.objects.none()

QuerySet合并不能用简单的相加,应为: QuerySet-1 | QuerySet-2

查询接口权限控制

Django 内置权限扩展案例详解

如上图系统中有很多功能是需要根据项目、环境查询对应的DB信息的,对于此类接口也需要控制用户只能查询自己有权限读的DB实例,管理员能查看所有,代码如下:

def get_project_database(request, project, environment):
 if request.method == 'GET':
 _jsondata = {}

 if request.user.is_superuser:
 # 返回所有项目和环境匹配的DB
 _lists = Mysql.objects.filter(
 project_id=int(project),
 environment=int(environment)
 )

 _jsondata = {i.id: i.database for i in _lists}
 else:
 # 只返回用户有权限查询的DB
 _user_groups = request.user.groups.all()

 for group in _user_groups:
 # 循环mysql表中有read_groups权限的所有组
 for mysql in group.read.all():
  if mysql.project_id == int(project) and mysql.environment == int(environment):
  _jsondata[mysql.id] = mysql.database

 return JsonResponse(_jsondata)

实现思路与上边类似,只是多了一步根据项目和环境再进行判断

需要根据group去反查都有哪些DB实例包含了该组,这里用到了M2M的related_name属性: group.read.all()

更多关于Django ORM查询的内容可以看这篇文章 Django model select的各种用法详解 有详细的总结

执行操作权限控制

除了上边的两个场景之外我们还需要在执行具体的操作之前去判断是否有权限,例如执行审核操作前判断用户是否对此DB有写权限

有很多地方都需要做这个判断,所以把这个权限判断单独写个方法来处理,代码如下:

def check_permission(perm, mysql, user):
 # 如果用户是超级管理员则有权限
 if user.is_superuser:
 return True

 # 取出用户所属的所有组
 _user_groups = user.groups.all()

 # 取出Mysql对应权限的所有组
 if perm == 'read':
 _mysql_groups = mysql.read_groups.all()
 if perm == 'write':
 _mysql_groups = mysql.write_groups.all()

 # 用户组和DB权限组取交集,有则表示有权限,否则没有权限
 group_list = list(set(_user_groups).intersection(set(_mysql_groups)))

 return False if len(group_list) == 0 else True

实现思路是:根据传入的第三个用户参数,来获取到用户所有的组,然后根据传入的第一个参数类型读取或写入和第二个参数DB实例来获取到有权限的所有组,然后对两个组取交集,交集不为空则表示有权限,为空则没有

M2M的 .all() 取出来的结果是个list,两个list取交集的方法为: list(set(list-A).intersection(set(list-B)))

view中使用就很简单了,如下:

def query(request):
 if request.method == 'POST':
 postdata = request.body.decode('utf-8')
 _host = get_object_or_404(Mysql, id=int(postdata.get('database')))

 # 检查用户是否有DB的查询权限
 if check_permission('read', _host, request.user) == False:
 return JsonResponse({'state': 0, 'message': '当前用户没有查询此DB的权限'})

写在最后

1.  Django有第三方的基于object的权限管理模块Django-guardian,本项目没有使用主要是因为一来权限需求并不复杂,自己实现也很方便,二来个人在非必要的情况下并不喜欢引用过多第三方的包,后续升级维护都是负担

2.  方案和代码不尽完美,各位有更好的方案建议或更优雅的代码写法欢迎与我交流

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python使用append合并两个数组的方法
Apr 28 Python
python简单实现旋转图片的方法
May 30 Python
Python实现多并发访问网站功能示例
Jun 19 Python
对numpy中的where方法嵌套使用详解
Oct 31 Python
python找出完数的方法
Nov 12 Python
python logging模块书写日志以及日志分割详解
Jul 22 Python
Python 网络编程之TCP客户端/服务端功能示例【基于socket套接字】
Oct 12 Python
Python列表list常用内建函数实例小结
Oct 22 Python
使用Python实现 学生学籍管理系统
Nov 26 Python
Python虚拟环境库virtualenvwrapper安装及使用
Jun 17 Python
Python3爬虫mitmproxy的安装步骤
Jul 29 Python
python批量合成bilibili的m4s缓存文件为MP4格式 ver2.5
Dec 01 Python
python三方库之requests的快速上手
Mar 04 #Python
django的ORM模型的实现原理
Mar 04 #Python
Python中按值来获取指定的键
Mar 04 #Python
python实现合并两个排序的链表
Mar 03 #Python
Python给图像添加噪声具体操作
Mar 03 #Python
django配置连接数据库及原生sql语句的使用方法
Mar 03 #Python
更新修改后的Python模块方法
Mar 03 #Python
You might like
浅析虚拟主机服务器php fsockopen函数被禁用的解决办法
2013/08/07 PHP
destoon实现资讯信息前面调用它所属分类的方法
2014/07/15 PHP
CI框架中site_url()和base_url()的区别
2015/01/07 PHP
php实现在服务器端调整图片大小的方法
2015/06/16 PHP
php实现微信小程序授权登录功能(实现流程)
2019/11/13 PHP
php设计模式之策略模式实例分析【星际争霸游戏案例】
2020/03/26 PHP
PHP强制转化的形式整理
2020/05/22 PHP
Nigma vs Liquid BO3 第二场2.13
2021/03/10 DOTA
js表数据排序 sort table data
2009/02/18 Javascript
引用外部脚本时script标签关闭的写法
2014/01/20 Javascript
jquery判断输入密码两次是否相等
2020/04/22 Javascript
JavaScript正则表达式小结(test|match|search|replace|split|exec)
2016/12/08 Javascript
浅谈JsonObject中的key-value数据解析排序问题
2017/12/06 Javascript
js嵌套的数组扁平化:将多维数组变成一维数组以及push()与concat()区别的讲解
2019/01/19 Javascript
如何对react hooks进行单元测试的方法
2019/08/14 Javascript
基于JS抓取某高校附近共享单车位置 使用web方式展示位置变化代码实例
2019/08/27 Javascript
[02:25]DOTA2英雄基础教程 虚空假面
2014/01/02 DOTA
[03:41]2018完美盛典-《Fight With Us》
2018/12/16 DOTA
零基础写python爬虫之HTTP异常处理
2014/11/05 Python
使用IronPython把Python脚本集成到.NET程序中的教程
2015/03/31 Python
Python自定义scrapy中间模块避免重复采集的方法
2015/04/07 Python
python通过ftplib登录到ftp服务器的方法
2015/05/08 Python
Python实现邮件的批量发送的示例代码
2018/01/23 Python
基于Python打造账号共享浏览器功能
2019/05/30 Python
python科学计算之narray对象用法
2019/11/25 Python
pytorch::Dataloader中的迭代器和生成器应用详解
2020/01/03 Python
Python使用Pyqt5实现简易浏览器(最新版本测试过)
2020/04/27 Python
浅谈python处理json和redis hash的坑
2020/07/16 Python
Bally巴利中国官网:经典瑞士鞋履、手袋及配饰奢侈品牌
2018/10/09 全球购物
学历公证委托书
2014/04/09 职场文书
优秀少先队大队辅导员事迹材料
2014/05/04 职场文书
个人工作表现评价材料
2014/09/21 职场文书
领导干部作风建设总结
2014/10/23 职场文书
清洁工岗位职责
2015/02/13 职场文书
人身损害赔偿协议书
2016/03/22 职场文书
扩展多台相同的Web服务器
2021/04/01 Servers