Django内置权限扩展案例
当Django的内置权限无法满足需求的时候就自己扩展吧~
背景介绍
overmind项目使用了Django内置的权限系统,Django内置权限系统基于model层做控制,新的model创建后会默认新建三个权限,分别为:add、change、delete,如果给用户或组赋予delete的权限,那么用户将可以删除这个model下的所有数据。
原本overmind只管理了我们自己部门的数据库,权限设置只针对具体的功能不针对细粒度的数据库实例,例如用户A 有审核的权限,那么用户A 可以审核所有的DB,此时使用内置的权限系统就可以满足需求了,但随着系统的不断完善要接入其他部门的数据库管理,这就要求针对不同用户开放不同DB的权限了,例如A部门的用户只能操作A部门的DB,Django内置基于model的权限无法满足需求了。
实现过程
先来确定下需求:
- 保持原本的基于功能的权限控制不变,例如用户A有查询权限,B有审核权限
- 增加针对DB实例的权限控制,例如用户A只能查询特定的DB,B只能审核特定的DB
对于上边需求1用内置的权限系统已经可以实现,这里不赘述,重点看下需求2,DB信息都存放在同一个表里,不同用户能操作不同的DB,也就是需要把每一条DB信息与有权限操作的用户进行关联,为了方便操作,我们考虑把DB跟用户组关联,在用户组里的用户都有权限,而操作类型经过分析主要有两类读和写,那么需要给每个MySQL实例添加两个字段分别记录对此实例有读和写权限的用户组
如下代码在原来的model基础上添加read_groups
和write_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确定了,接下来我们分三部分详细介绍下权限验证的具体实现
列表页权限控制
如上图列表页,每个用户进入系统后只能查看自己有读权限的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
查询接口权限控制
如上图系统中有很多功能是需要根据项目、环境查询对应的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.databasereturn 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的权限'})
写在最后
- Django有第三方的基于object的权限管理模块Django-guardian,本项目没有使用主要是因为一来权限需求并不复杂,自己实现也很方便,二来个人在非必要的情况下并不喜欢引用过多第三方的包,后续升级维护都是负担
- 方案和代码不尽完美,各位有更好的方案建议或更优雅的代码写法欢迎与我交流
相关文章推荐阅读:
- Django+JWT实现Token认证
- 我们自研的那些Devops工具
转载于:https://www.cnblogs.com/37Y37/p/10514575.html
Django内置权限扩展案例相关推荐
- Django内置分页扩展
url文件 urlpatterns = [path('admin/', admin.site.urls),path('index1.html/', views.index1), ] views文件 f ...
- Django内置Admin
Django内置的Admin是对于model中对应的数据表进行增删改查提供的组件,使用方式有: 依赖APP:django.contrib.authdjango.contrib.contenttypes ...
- ES6新增语法与内置对象扩展
技术交流QQ群:1027579432,欢迎你的加入! 欢迎关注我的微信公众号:CurryCoder的程序人生 1.什么是ES6 ES的全称是ECMAScript,它是由ECMA国际标准化组织制定的一项 ...
- django 内置 admin
Django内置的Admin是对于model中对应的数据表进行增删改查提供的组件使用方式有依赖APPdjango.contrib.authdjango.contrib.contenttypesdjan ...
- 〖Python〗-- Django内置Admin
[Django内置Admin] Django内置的Admin是对于model中对应的数据表进行增删改查提供的组件,使用方式有: 依赖APP:django.contrib.authdjango.cont ...
- Django - 内置admin
Django内置的Admin是对于model中对应的数据表进行增删改查提供的组件,使用方式有: Django内置的Admin是对于model中对应的数据表进行增删改查提供的组件,使用方式有:复制代码依 ...
- es6 内置对象扩展rest, Arry 扩展方法Array.from(),find(), findIndex(),includes()
1.es6内置对象扩展rest实参 2. Arry 扩展方法 (1)构造函数方法:Array.from() (2) find() (3) findIndex() (4)includ ...
- Django内置的用户认证
认证登陆 在进行用户登陆验证的时候,如果是自己写代码,就必须要先查询数据库,看用户输入的用户名是否存在于数据库中: 如果用户存在于数据库中,然后再验证用户输入的密码,这样一来就要自己编写大量的代码. ...
- django 内置标签与过滤器
#内置标签与过滤器 本文档介绍了Django的内置模板标签和过滤器. 我们推荐尽可能使用 自动文档,同时也可以自行编辑任何已安装的自定义标签或过滤器的文档. ##内置标记引用 ###autoescap ...
最新文章
- 信号转换| 如何使用模拟电路完成对方波信号倍频PWM转换?
- MySQL单表恢复方法
- 2018年1月29日
- Tomcat绿色版启动startup.bat一闪问题的解决方法!
- 客户端相关知识学习(四)之H5页面如何嵌套到APP中
- H264和AAC合成FLV案例
- php使用pdf2htmlex,转换 HTML 与 PDF 格式文档的神器
- Debian 新负责人发表演讲:Debian 的现状与面临的一些问题
- esp8266原理图_ESP32/ESP8266使用MicroPython控制DHT11/DHT22
- 亚马逊最大无人售货超市开张,云端结账随拿随走
- 加密解密工具 之 希尔密码
- android 朗读tts_如何设置Android以大声朗读您的文本
- startActivityForResult用法
- python自动化运维开发入门-张子夜-专题视频课程
- VHD轉換VHDX格式
- 服务器:php-fpm线程卡死导致网站无响应
- java百万级大数据量导出
- 2022 高德地图的使用 获取当前城市
- 地震--《孩子,快抓紧妈妈的手》
- Uva 11374 - Airport Express(枚举+最短路)
热门文章
- 【Python学习】 - skimage包
- 【 CodeForces - 799A 】Carrot Cakes(模拟,细节,有坑)
- python 协程池gevent.pool_进程池\线程池,协程,gevent
- 计算日期时间间隔,可计算情侣计算在一起的。精确到,天,时,分,秒(输入的日期距离现在有:819天19670小时1180221分钟70813262秒)
- 日期时间格式转化为方便理解的格式
- (多线程)leetcode1116. 打印零与奇偶数
- 先序中序数组推后序数组
- 深搜DFS\广搜BFS 图初步入门
- lua的VS或者VC环境的搭建调试
- 周志华《Machine Learning》 学习笔记系列(1)--绪论