0X00 Django 中的权限结构、定义
我们知道在创建了一个 Django 项目之后,默认就有两个公开可用的 model:User 和 Group,这两个 model 的一项功能就是用来做权限管理的。系统中会有很多项权限,单个 user 可以配置拥有哪些权限,也可以将权限配置给 group。然后校验单个权限的时候其实就是将 user 本身的权限,和 user 所在的所有组的权限做一个并集,看本次操作的权限是否在这个并集里。在,那就校验通过;不在,那就只有 HTTP 403 了。

HTTP 401 和 HTTP 403 的区别:401 的描述是 Forbidden,而 401 是 Unauthorized,前者是没有权限,而后者干脆没通过认证。举个例子,你想查看公司财务的详细报表,财务经理一看你就是个一线小程序员,就给你一个 401,告诉你这不是你可以看的东西。如果你想看别的公司财务的详细报表,别人公司财务经理一看你根本不是他们公司的人,就直接给你了个 401 了。(俗话说十个比喻九个不准,我这个比喻当然也并不非常准确,不过对于分不清 401 和 403 的同学而言应该也问题不大🤣)
Django 自己对每一个 model 都创建了 create,update,delete 的权限,我们可以直接拿来用,也可以自己添加新权限。Django 自己是针对各个 model 做的权限,所以最简单的权限建立是在 model 层进行的。就比如下面这种,如果我想要为 Student 这个 model 建立相关的权限,就可以通过修改 Meta
类里的 permissions
来实现。
不过这里也看到了,每次对权限进行 CUD 的时候都是在改 model 的,所以每次改动完 model 记得都要进行一次migrate
操作才行。不过不用担心性能问题,这个 migrate 只对 Permission 表进行 CUD 操作,而并非改表,所以非常快就搞定了。注意的一点是,不管你把这些 permissions 写在哪个 model 下,最终他们创建好的数据都还是在 Permission 表里,也就是数据库(我这里用的是 MySQL)里的auth_permission
表了,这个表结构和数据是下面这样的。
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | <null> | auto_increment |
| name | varchar(255) | NO | | <null> | |
| content_type_id | int(11) | NO | MUL | <null> | |
| codename | varchar(100) | NO | | <null> | |
+-----------------+--------------+------+-----+---------+----------------+
+-----+------------------------------------+-----------------+-------------------------------+
| id | name | content_type_id | codename |
+-----+------------------------------------+-----------------+-------------------------------+
| 1 | Can add log entry | 1 | add_logentry |
| 2 | Can change log entry | 1 | change_logentry |
| 3 | Can delete log entry | 1 | delete_logentry |
| 4 | Can add group | 2 | add_group |
| 5 | Can change group | 2 | change_group |
| 6 | Can delete group | 2 | delete_group |
| 7 | Can add permission | 3 | add_permission |
| 8 | Can change permission | 3 | change_permission |
| 9 | Can delete permission | 3 | delete_permission |
| 10 | Can add user | 4 | add_user |
| 11 | Can change user | 4 | change_user |
| 12 | Can delete user | 4 | delete_user |
+-----+------------------------------------+-----------------+-------------------------------+
0X01 在 Django 中校验与分配权限
权限在表里之后“唯一”生效的地方就是 django-admin 了,就是说如果登录 django-admin 的用户没有add_user
的权限,那你创建用户的时候就会被拒绝。但是我们平时更多的时候并不是在 django-admin 里,而是自己实现的前台页面,那该怎么自己校验权限呢?Django 中给 user 实例了两个方法:user.has_perm
和user.has_perms
。显然,前者校验单个权限,后者校验多个权限。咱们先来看一下是如何校验的:
我们从 Django 源码中可以看到,如果正在校验的用户是“活跃的”而且是“超级管理员”,那就直接不校验了,通过。否则就去校验一波。同时校验多个权限的时候用了all
去逐个校验,遇到一个没有的权限也就 False 了。具体的细节这里就不多说了,感兴趣的话可以看一下源码,在django.contrib.auth.models.User
中。
然后要考虑的就是如何将权限分给用户了,我们可以在 Django 源码中看到 user 和 permission 的关联关系是:ManyToMany ,所以直接修改用户的user_permissions
字段就可以了。针对 group 也是一样的。
这里有一个比较干扰人的问题,就是为什么我的 codename 明明是 add_user 但是校验的时候却要用auth.add_user
。当是这个问题也是困扰了我比较久,还因为这个问题导致了我程序出现 bug(比如本来该校验car.open_door
的地方我校验了open_door
)。比较明显的一处是因为“重名“,因为我们 model 多了之后会出现好多处重名的权限名,所以校验的时候应该在codename 前面加上 app_name。可以看一下我们 settings 里面的 INSTALLED_APPS 配置,有一条就是django.contrib.auth
。
0X02 DRF 中的权限设计
我们在配置 DRF 的时候通常会在 settings 里写下类似这样的配置,这意味着给默认的 permission_class 设置成了IsAuthenticated
,也就是说登录即可。
但是这也只是一个默认的权限配置,你用 DRF 的ViewSet
创建的新 API 默认是登录即可,但是总会有其他的情况,这种时候通常来说我我们会重写 ViewSet 中的两个方法:get_permissions
和get_queryset
。先说正经的权限,也就是get_permissions
好了。
我们从源码中可以看到这个方法其实很很简单,就只是把self.permission_classes一遍,通过 codename 进行实例化,最终返回一个实例化的 permission 列表。然后在
check_permissions或者
check_object_permissions的时候对这个列表轮询一遍,调用 permission 实例的
has_object_permission(request, view)`方法来判断是否有权限执行本次请求,最终通过返回的 True/False 来确定是否有权限继续操作,如果没有权限就直接终止请求了。
根据上述条件,我们就可以自己写一个权限校验类了,一个简单的实现时下面这种样子的:
0X03 DRF 如何校验权限
现在我们知道 Django 和 DRF 中的权限大概是怎么实现的了,那么具体怎么用在 DRF 框架的 web 程序中呢?下面看一段实例代码。这坨代码就是我们最常见的一个 DRF 开出来的 ViewSet 了,同时支持GET/POST/PUT/PATCH/DELETE
五种 HTTP 方法。
在 DRF 的 ViewSet 中,可以使用 self.action 来访问到当前请求的 function。默认情况下的 CRUD 对应的方法名是:list/create/retrieve/partial_update/update/delete
这五个,分别对应了GET /
,POST /
,GET /id/
,PATCH /id/
,PUT /id/
,DELETE /id/
我们可以从上面看到有两个地方在校验权限,一个是get_permissions()
,另一个是get_queryset()
。前者是严谨的“权限”,也就是说“你想要执行某某某操作,你就需要拥有某某某权限,没有就不信”;后者是广义的“权限”,会从大到小来校验,比如这里可以先判断你是否拥有查看所有人信息的权限,有,就给你所有人的信息;没有,就看你能不能看班里人的信息,有,就给你班里人的信息;没有,就只返回给你自己的数据。
这两种用法是在日常工作中用法最多的了。其他再有的话就是在 serializer 的 validate 中进行的校验,比如有一个功能叫做“强制删除”,这个功能可以跳过一切检测(比如检测学生是否在校,学生是否毕业超过3 年,学生饭卡是否还有钱),就直接删除。这种权限可能只会分配给高级别管理员使用,但是又不可能针对这种功能再单独开新的 API 来做,这样太奇怪了。所以我们可以通过一个参数来控制前置删除,比如force_delete
。这样我们就可以写出类似下面的代码
0X04 THE END
今天是个美好的周日,中午的时候想要喝了咖啡写篇博客就去玩 火焰纹章 的的,然而越看这个权限越越觉得自己不懂,之前一度以为自己挺明白的,结果一直是处在知其然的状态,并没有知其所以然(虽然现在知道也也不彻底)。然后就一点点看之前的代码,看实现方案,看着看着又得进去看源码,结果计划一个多小时搞定的东西搞了快四个小时。不过还好,也不亏,收获颇丰~
感谢您的支持
如果您喜欢这篇文章,可以通过以下方式支持我:
评论区