Django 内置权限扩展:实现基于数据库实例的细粒度访问控制

Ivy
Ivy
发布时间:2026-10-13| 6人浏览过
原创

本文详解如何在 Django 内置权限基础上,通过扩展 Model 添加 read_groups 和 write_groups 字段,实现针对具体数据库实例的细粒度读写权限控制。文章涵盖列表页过滤、查询接口鉴权及执行前权限校验三种场景,利用 ManyToMany 关系与 QuerySet 合并技巧,提供完整代码示例与实现思路,帮助开发者解决多部门数据库管理中的权限隔离问题。

当 Django 的内置权限机制无法满足业务需求时,开发者需要自行进行扩展设计。

背景介绍

背景介绍

overmind 项目采用了 Django 内置的权限系统,该系统基于 Model 层进行控制。每当创建新的 Model 时,Django 默认生成三个权限:add、change 和 delete。若给用户或用户组赋予 delete 权限,该用户即可删除该 Model 下的所有数据。

起初,overmind 仅管理本部门数据库,权限配置仅针对功能模块而非细粒度的数据库实例。例如,用户 A 拥有审核权限,即可审核所有数据库,此时内置权限系统足以满足需求。然而,随着系统完善并接入其他部门的数据库管理,需实现针对不同用户开放不同数据库的权限(如 A 部门用户仅能操作 A 部门数据库)。Django 基于 Model 的内置权限已无法直接满足此类需求。

权限扩展实现方案

实现过程

首先明确需求:

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

2. 增加针对数据库实例的权限控制,例如用户 A 仅能查询特定的数据库,用户 B 仅能审核特定的数据库。

对于需求 1,Django 内置权限系统已能实现,此处不再赘述。重点在于需求 2:数据库信息存储在同一张表中,不同用户需操作不同的数据库记录。这意味着需要将每条数据库记录与有权限操作的用户进行关联。为便于管理,我们将数据库与用户组关联,即属于该用户组的用户均拥有权限。经分析,操作类型主要分为“读”和“写”两类,因此需为每个 MySQL 实例添加两个字段,分别记录拥有读权限和写权限的用户组。

在原有 Model 基础上添加 read_groups 和 write_groups 字段。数据库实例与用户组之间应为 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

查询接口鉴权逻辑

查询接口权限控制

如上图所示,系统中存在许多需根据项目、环境查询对应数据库信息的接口。对此类接口也需控制用户仅能查询其有权限读取的数据库实例,管理员可查看全部。代码如下:


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)

实现思路与前文类似,但增加了根据项目和环境进行进一步判断的步骤。

需根据用户组反查包含该组的数据库实例,此处利用了 M2M 的 related_name 属性:group.read.all()

更多关于 Django ORM 查询的内容可参考文章《Django model select 的各种用法详解》,其中有详细总结。

执行操作前的权限校验

执行操作权限控制

除上述两个场景外,还需在执行具体操作前判断用户是否拥有权限。例如,在执行审核操作前,需判断用户对该数据库是否拥有写权限。

由于多处需进行此类判断,故将权限判断逻辑封装为独立方法处理,代码如下:


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

实现思路为:根据传入的第三个用户参数,获取该用户所属的所有用户组;再根据传入的第一个参数(读取或写入)和第二个参数(数据库实例),获取拥有相应权限的所有用户组;最后对两组取交集,若交集非空则表示有权限,否则无权限。

M2M 的 .all() 取出的结果是列表,两个列表取交集的方法为: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 有基于对象的第三方权限管理模块 Django-guardian,本项目未使用的原因在于:一来权限需求并不复杂,自行实现较为便捷;二来个人倾向于非必要不引用过多第三方包,以免增加后续升级与维护负担。

2. 本方案与代码尚不完美,若有更优方案建议或更优雅的代码写法,欢迎交流。

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

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。