今天老男孩IT教育Python教学总监alex带你开通Django之路
需求讨论
权限设计
代码设计
自定义权限钩子
业务场景分析
假设我们在开发一个培训机构的客户关系管理系统系统分客户管理、学员管理、教学管理3个大模块每个模块大体功能如下
客户管理
销售人员可以录入客户信息对客户进行跟踪为客户办理报名手续
销售人员可以修改自己录入的客户信息
客户信息不能删除
销售主管可以查看销售报表
学员管理
学员可以在线报名
学员可以查看自己的报名合同、学习有效期
学员可以在线提交作业 、查看自己的成绩
教学管理
管理员可以创建新课程、班级
讲师可以创建上课纪录
讲师可以在线点名、批作业
从上面的需求中 我们至少提取出了5个角色普通销售、销售主管、学员、讲师、管理员 他们能做的事情都是不一样的
如何设计一套权限组件来实现对上面各种不同功能进行有效的权限控制呢我们肯定不能LOW到为每个动作都一堆代码来控制权限对吧 这些表面上看着各种不尽相同的功能,肯定是可以提取出一些相同的规律的仔细分析其实每个功能本质上都是一个个的动作如果能把动作再抽象中具体权限条目然后把这些权限条目 再跟用户关联每个用户进行这个动作就检查他没有这个权限不就实现权限的控制了么由于这个系统是基于WEB的B/S架构我们可以把每个动作的构成提取成以下的元素
一个动作 = 一条权限 = 一个url + 一种请求方法(get/post/put...) + 若干个请求参数
那我们接下来需要做的就是把 一条条的权限条目定义出来然后跟用户关联上就可以了
开发中需要的权限定义
什么是权限
权限 就是对 软件系统 中 各种资源 的 访问和操作的控制
什么时资源
在软件系统中数据库、内存、硬盘里数据都是资源资源就是数据
动作
资源本身是静态的 必须通过合适的动作对其进行访问和操作我们说要控制权限其实本质上是要对访问 软件中各种数据资源的动作进行控制
动作又可以分为2种
资源操作动作访问和操作各种数据资源比如访问数据库或文件里的数据
业务逻辑事件动作访问和操作的目的不是数据源本身而是借助数据源而产生的一系列业务逻辑比如批量往远程 主机上上传一个文件你需要从数据库中访问主机列表但你真正要操作的是远程的主机这个远程的主机严格意义上来并不是你的数据资源而是这个资源代表的实体。
权限授权
权限的使用者可以是具体的个人、亦可以是其它程序 这都没关系我们可以把权限的授权主体统称为用户 无论这个用户后面是具体的人还是一个程序对权限控制组件来讲都不影响 。
权限必然是需要分组的把一组权限 分成一个组授权给特定的一些用户分出来的这个组就可以称为角色。
权限 应该是可以叠加的
权限组件的设计与代码实现
我们把权限组件的实现分3步权限条目的定义 权限条目与用户的关联权限组件与应用的结合
权限条目的定义
我们前面讲过以下概念 现在需要做的就是把我们系统中所有的需要控制的权限 所对应的动作 提取成 一条条 url+请求方法+参数的集合就可以以下是提取出来的几条权限
一个动作 = 一条权限 = 一个url + 一种请求方法(get/post/put...) + 若干个请求参数
1
2
3
4
5
6
7
8
|
perm_dic
=
{
'crm_table_index'
:[
'table_index'
,
'GET'
,[],{},],
#可以查看CRM APP里所有数据库表
'crm_table_list'
:[
'table_list'
,
'GET'
,[],{}],
#可以查看每张表里所有的数据
'crm_table_list_view'
:[
'table_change'
,
'GET'
,[],{}],
#可以访问表里每条数据的修改页
'crm_table_list_change'
:[
'table_change'
,
'POST'
,[],{}],
#可以对表里的每条数据进行修改
}
|
字典里的key是权限名 一会我们需要用过这些权限名来跟用户进行关联
后面values列表里第一个值如'table_index'是django中的url name在这里必须相对的url name, 而不是绝对url路径因为考虑到django url正则匹配的问题搞绝对路径不好控制。
values里第2个值是http请求方法
values里第3个[]是要求这个请求中必须带有某些参数但不限定对数的值是什么
values里的第4个{}是要求这个请求中必须带有某些参数并且限定所带的参数必须等于特定的值
有的同学看了上面的几条权限定义后提出疑问说你这个权限的控制好像还是粗粒度的 比如我想控制用户只能访问 客户 表里的 一条或多条特定的用户怎么办
哈这个问题很好但很容易解决呀只需要在[] or {}里指定参数就可呀比如要求http请求参数中必须包括指定的参数举个例子 我的客户表如下
Customer表
里面的status字段是用来区分客户是否报名的 我现在的需求是只允许 用户访问客户来源为qq群且 已报名的 客户你怎么控制
通过分析我们得出这个动作的url为
1
|
http:
/
/
127.0
.
0.1
:
9000
/
kingadmin
/
crm
/
customer
/
?source
=
qq&status
=
signed
|
客户来源参数是source,报名状态为status那我的权限条目就可以配置成
1
|
'crm_table_list'
:[
'table_list'
,
'GET'
,[],{
'source'
:
'qq'
,
'status'
:
'signed'
}]
|
权限条目与用户的关联
我们并没有像其它权限系统一样把权限定义的代码写到了数据里了也许是因为我懒不想花时间去设计存放权限的表结构but anyway,基于现有的设计 我们如何把权限条目与 用户关联起来呢
good news is 我们可以直接借用django自带的权限系统 大家都知道 django admin 自带了一个简单的权限组件允许把用户在使用admin过程中控制到表级别的增删改查程度但没办法对表里的某条数据控制权限即要么允许访问整张表要么不允许访问实现不了只允许用户访问表中的特定数据的控制。
我们虽然没办法对通过自带的django admin 权限系统实现想要的权限控制但是可以借用它的 权限 与用户的关联 逻辑自带的权限系统允许用户添加自定义权限条目方式如下
1
2
3
4
5
6
7
8
|
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"
),
)
|
这样就添加了3条自定义权限的条目 然后 manage.py migrate 就可以在django自带的用户表里的permissions字段看到你刚添加的条目。
只要把刚添加的几条权限移动的右边的框里那这个用户就相当于有相应的权限了以后你在代码里通过以下语句就可以判定用户是否有相应的权限。
1
|
user.has_perm(
'app.view_task'
)
|
看到这有的同学还在懵逼这个自带的权限跟我们刚才自己定义的权限条目有半毛钱关系么聪明的同学已经看出来了 只要我们把刚才自己定义的perm_dic字典里的所有key在这个META类的permissions元组里。就相当于把用户和它可以操作的权限关联起来了这就省掉了我们必须自己写权限与用户关联所需要的代码了
权限组件与应用的结合
我们希望我们的权限组件是通用的可插拔的它一定要与具体的业务代码分离以后可以轻松把这个组件移植到其它的项目里去因此这里我们采用装饰器的模式把权限的检查、控制封装在一个装饰器函数里想对哪个Views进行权限控制就只需要在这个views上加上装饰器就可以了。
1
2
3
|
@check_permission
def
table_change(request,app_name,table_name,obj_id):
.....
|
那这个@check_permission装饰器里干的事情就是以下几步
拿到用户请求的url+请求方法+参数到我们的的perm_dic里去一一匹配
当匹配到了对应的权限条目后就拿着这个条目所对应的权限名和当前的用户 调用request.user.has_perm(权限名)
如果request.user.has_perm(权限名)返回为True,就认为该用户有权限 直接放行否则则返回403页面
权限检查代码
加入自定义权限
仔细按上面的步骤走下来并玩了一会的同学可能会发现一个问题这个组件对有些权限是控制不到的 就是涉及到一些业务逻辑的权限没办法控制 比如 我只允许 用户访问自己创建的客户数据这个你怎么控制
通过控制用户的请求参数是没办法实现的 因为你获取到的request.user是个动态的值你必须通过代码来判断这条数据是否是由当前请求用户创建的。 类似的业务逻辑还有很多你怎么搞
仔细思考了10分钟即然这里必须涉及到必须允许开发人员通过自定义一些业务逻辑代码来判断用户是否有权限的话那我在我的权限组件里再提供一个权限自定义函数不就可以了开发者可以把自定的权限逻辑写到函数里我的权限组件 自动调用这个函数只要返回为True就认为有权限就可以啦
加入了自定义权限钩子的代码
权限配置条目
1
2
3
|
'crm_can_access_my_clients'
:[
'table_list'
,
'GET'
,[],
{
'perm_check'
:
33
,
'arg2'
:
'test'
},
custom_perm_logic.only_view_own_customers],
|
看最后面我们加入的only_view_own_customers就是开发人员自已加的权限控制逻辑里面想怎么写就怎么写
1、def only_view_own_customers(request,*args,**kwargs):
2、 print('perm test',request,args,kwargs)
3、
4、 consultant_id = request.GET.get('consultant')
5、 if consultant_id:
6、 consultant_id = int(consultant_id)
7、
8、 print("consultant=1",type(consultant_id))
9、
10、 if consultant_id == request.user.id:
11、 print("\033[31;1mchecking [%s]'s own customers, pass..\033[0m"% request.user)
12、 return True
13、 else:
14、 print("\033[31;1muser can only view his's own customer...\033[0m")
15、 return False
这样万通且通用的权限框架就开发完毕了权限的控制粒度可粗可细、可深可浅包君满意以后要移植到其它django项目时 你唯一需要改的就是配置好perm_dic里的权限条目
用完觉得好记得点赞噢
更多精彩请关注老男孩IT教育python学院