概述
本节内容
需求讨论
权限设计
代码设计
自定义权限钩子
假设我们在开发一个培训机构的 客户关系管理系统,系统分客户管理、学员管理、教学管理3个大模块,每个模块大体功能如下
客户管理 销售人员可以录入客户信息,对客户进行跟踪,为客户办理报名手续 销售人员可以修改自己录入的客户信息 客户信息不能删除 销售主管可以查看销售报表
学员管理 学员可以在线报名 学员可以查看自己的报名合同、学习有效期 学员可以在线提交作业 、查看自己的成绩
教学管理 管理员可以创建新课程、班级 讲师可以创建上课纪录 讲师可以在线点名、批作业
从上面的需求中, 我们至少提取出了5个角色,普通销售、销售主管、学员、讲师、管理员, 他们能做的事情都是不一样的
如何设计一套权限组件来实现对上面各种不同功能进行有效的权限控制呢?我们肯定不能LOW到为每个动作都一堆代码来控制权限对吧? 这些表面上看着各种不尽相同的功能,肯定是可以提取出一些相同的规律的,仔细分析,其实每个功能本质上都是一个个的动作,如果能把动作再抽象中具体权限条目,然后把这些权限条目 再跟用户关联,每个用户进行这个动作,就检查他没有这个权限,不就实现权限的控制了么?由于这个系统是基于WEB的B/S架构,我们可以把每个动作的构成 提取成以下的元素
一个动作 = 一条权限 = 一个url + 一种请求方法(get/post/put...) + 若干个请求参数
那我们接下来需要做的,就是把 一条条的权限条目定义出来,然后跟用户关联上就可以了!
权限 就是对 软件系统 中 各种资源 的 访问和操作的控制!
在软件系统中,数据库、内存、硬盘里数据都是资源,资源就是数据!
资源本身是静态的, 必须通过合适的动作对其进行访问和操作,我们说要控制权限,其实本质上是要对访问 软件中各种数据资源的动作进行控制
动作又可以分为2种:
资源操作动作:访问和操作各种数据资源,比如访问数据库或文件里的数据
业务逻辑事件动作:访问和操作的目的不是数据源本身,而是借助数据源而产生的一系列业务逻辑,比如批量往远程 主机上上传一个文件,你需要从数据库中访问主机列表,但你真正要操作的是远程的主机,这个远程的主机,严格意义上来并不是你的数据资源,而是这个资源代表的实体。
我们把权限组件的实现分3步,权限条目的定义, 权限条目与用户的关联,权限组件与应用的结合
我们前面讲过以下概念, 现在需要做的,就是把我们系统中所有的需要控制的权限 所对应的动作 提取成 一条条 url+请求方法+参数的集合就可以
一个动作 = 一条权限 = 一个url + 一种请求方法(get/post/put...) + 若干个请求参数
以下是提取出来的几条权限
'crm_table_index':['table_index','GET',[],{},],#可以查看CRM APP里所有<a href="https://www.jb51.cc/tag/shujuku/" target="_blank" class="keywords">数据库</a>表
'crm_table_list':['table_list',{}],#可以查看每张表里所有的数据
'crm_table_list_view':['table_change',#可以访问表里每条数据的<a href="https://www.jb51.cc/tag/xiugai/" target="_blank" class="keywords">修改</a>页
'crm_table_list_change':['table_change','POST',#可以对表里的每条数据进行<a href="https://www.jb51.cc/tag/xiugai/" target="_blank" class="keywords">修改</a>
}
字典里的key是权限名, 一会我们需要用过这些权限名来跟用户进行关联
有的同学看了上面的几条权限定义后,提出疑问,说你这个权限的控制好像还是粗粒度的, 比如我想控制用户只能访问 客户 表里的 一条或多条特定的用户怎么办?
哈,这个问题很好,但很容易解决呀,只需要在[] or {}里指定参数就可呀,比如要求http请求参数中必须包括指定的参数,举个例子, 我的客户表如下:
里面的status字段是用来区分客户是否报名的, 我现在的需求是,只允许 用户访问客户来源为qq群且 已报名的 客户,你怎么控制 ?
通过分析我们得出,这个动作的url为
客户来源参数是source,报名状态为status,那我的权限条目就可以配置成
我们并没有像其它权限系统一样把权限定义的代码写到了数据库里了,也许是因为我懒,不想花时间去设计存放权限的表结构,but anyway,基于现有的设计 ,我们如何把权限条目与 用户关联起来呢?
good news is 我们可以直接借用django自带的权限系统 ,大家都知道 django admin 自带了一个简单的权限组件,允许把用户在使用admin过程中控制到表级别的增删改查程度,但没办法对表里的某条数据控制权限,即要么允许访问整张表,要么不允许访问,实现不了只允许用户访问表中的特定数据的控制。
我们虽然没办法对通过自带的django admin 权限系统实现想要的权限控制,但是可以借用它的 权限 与用户的关联 逻辑!自带的权限系统允许用户添加自定义权限条目,方式如下
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字段看到你刚添加的条目。
只要把刚添加 的几条权限 移动的右边的框里,那这个用户就相当于有相应的权限 了!以后,你在代码里通过以下语句,就可以判定用户是否有相应的权限。
看到这,有的同学还在蒙逼,这个自带的权限跟我们刚才自己定义的权限条目有半毛钱关系么?聪明的同学已经看出来了, 只要我们把刚才自己定义的perm_dic字典里的所有key在这个Meta类的permissions元组里。就相当于把用户和它可以操作的权限关联起来了!这就省掉了我们必须自己写权限与用户关联所需要的代码了
我们希望我们的权限组件是通用的,可插拔的,它一定要与具体的业务代码分离,以后可以轻松把这个组件移植到其它的项目里去,因此这里我们采用装饰器的模式,把权限的检查、控制封装在一个装饰器函数里,想对哪个Views进行权限控制,就只需要在这个views上加上装饰器就可以了。
那这个@check_permission装饰器里干的事情就是以下几步:
request </span>=<span style="color: #000000;"> args[0]
resolve_url_obj </span>=<span style="color: #000000;"> resolve(request.path)
current_url_name </span>= resolve_url_obj.url_name <span style="color: #008000;">#</span><span style="color: #008000;"> 当前url的url_name</span>
<span style="color: #0000ff;">print</span>(<span style="color: #800000;">'</span><span style="color: #800000;">---perm:</span><span style="color: #800000;">'</span><span style="color: #000000;">,request.user,request.user.is_authenticated(),current_url_name)
</span><span style="color: #008000;">#</span><span style="color: #008000;">match_flag = False</span>
match_key =<span style="color: #000000;"> None
</span><span style="color: #0000ff;">if</span> request.user.is_authenticated() <span style="color: #0000ff;">is</span><span style="color: #000000;"> False:
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> redirect(settings.LOGIN_URL)
</span><span style="color: #0000ff;">for</span> permission_key,permission_val <span style="color: #0000ff;">in</span><span style="color: #000000;"> perm_dic.items():
per_url_name </span>=<span style="color: #000000;"> permission_val[0]
per_method </span>= permission_val[1<span style="color: #000000;">]
perm_args </span>= permission_val[2<span style="color: #000000;">]
perm_<a href="https://www.jb51.cc/tag/kwargs/" target="_blank" class="keywords">kwargs</a> </span>= permission_val[3<span style="color: #000000;">]
</span><span style="color: #0000ff;">if</span> per_url_name == current_url_name: <span style="color: #008000;">#</span><span style="color: #008000;">matches current request url</span>
<span style="color: #0000ff;">if</span> per_method == request.method: <span style="color: #008000;">#</span><span style="color: #008000;">matches request method</span>
<span style="color: #008000;">#</span><span style="color: #008000;"> if not perm_args: #if no args defined in perm dic,then set this request to passed perm </span>
<span style="color: #008000;">#</span><span style="color: #008000;">逐个匹配参数,看每个参数时候都能对应的上。</span>
args_matched = False <span style="color: #008000;">#</span><span style="color: #008000;">for args only</span>
<span style="color: #0000ff;">for</span> item <span style="color: #0000ff;">in</span><span style="color: #000000;"> perm_args:
request_method_func </span>=<span style="color: #000000;"> getattr(request,per_method)
</span><span style="color: #0000ff;">if</span> request_method_func.get(item,None):<span style="color: #008000;">#</span><span style="color: #008000;"> request字典中有此参数</span>
args_matched =<span style="color: #000000;"> True
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
</span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">arg not match......</span><span style="color: #800000;">"</span><span style="color: #000000;">)
args_matched </span>=<span style="color: #000000;"> False
</span><span style="color: #0000ff;">break</span> <span style="color: #008000;">#</span><span style="color: #008000;"> 有<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a>参数不能匹配成功,则判定为假,<a href="https://www.jb51.cc/tag/tuichu/" target="_blank" class="keywords">退出</a>该循环。</span>
<span style="color: #0000ff;">else</span><span style="color: #000000;">:
args_matched </span>=<span style="color: #000000;"> True
</span><span style="color: #008000;">#</span><span style="color: #008000;">匹配有特定值的参数</span>
<a href="https://www.jb51.cc/tag/kwargs/" target="_blank" class="keywords">kwargs</a>_matched =<span style="color: #000000;"> False
</span><span style="color: #0000ff;">for</span> k,v <span style="color: #0000ff;">in</span><span style="color: #000000;"> perm_<a href="https://www.jb51.cc/tag/kwargs/" target="_blank" class="keywords">kwargs</a>.items():
request_method_func </span>=<span style="color: #000000;"> getattr(request,per_method)
arg_val </span>= request_method_func.get(k,None) <span style="color: #008000;">#</span><span style="color: #008000;"> request字典中有此参数</span>
<span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">perm <a href="https://www.jb51.cc/tag/kwargs/" target="_blank" class="keywords">kwargs</a> check:</span><span style="color: #800000;">"</span><span style="color: #000000;">,arg_val,type(arg_val),v,type(v))
</span><span style="color: #0000ff;">if</span> arg_val == str(v): <span style="color: #008000;">#</span><span style="color: #008000;">匹配上了特定的参数 及对应的 参数值, 比如,需要request 对象里必须有<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a>叫 user_id=3的参数</span>
<a href="https://www.jb51.cc/tag/kwargs/" target="_blank" class="keywords">kwargs</a>_matched =<span style="color: #000000;"> True
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
<a href="https://www.jb51.cc/tag/kwargs/" target="_blank" class="keywords">kwargs</a>_matched </span>=<span style="color: #000000;"> False
</span><span style="color: #0000ff;">break</span> <span style="color: #008000;">#</span><span style="color: #008000;"> 有<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a>参数不能匹配成功,则判定为假,<a href="https://www.jb51.cc/tag/tuichu/" target="_blank" class="keywords">退出</a>该循环。</span>
<span style="color: #0000ff;">else</span><span style="color: #000000;">:
<a href="https://www.jb51.cc/tag/kwargs/" target="_blank" class="keywords">kwargs</a>_matched </span>=<span style="color: #000000;"> True
match_results </span>=<span style="color: #000000;"> [args_matched,<a href="https://www.jb51.cc/tag/kwargs/" target="_blank" class="keywords">kwargs</a>_matched]
</span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">--->match_results </span><span style="color: #800000;">"</span><span style="color: #000000;">,match_results)
</span><span style="color: #0000ff;">if</span> all(match_results): <span style="color: #008000;">#</span><span style="color: #008000;">都匹配上了</span>
match_key =<span style="color: #000000;"> permission_key
</span><span style="color: #0000ff;">break</span>
<span style="color: #0000ff;">if</span><span style="color: #000000;"> all(match_results):
app_name,</span>*per_name = match_key.split(<span style="color: #800000;">'</span><span style="color: #800000;">_</span><span style="color: #800000;">'</span><span style="color: #000000;">)
</span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">--->matched </span><span style="color: #800000;">"</span><span style="color: #000000;">,match_results,match_key)
</span><span style="color: #0000ff;">print</span>(app_name,*<span style="color: #000000;">per_name)
perm_obj </span>= <span style="color: #800000;">'</span><span style="color: #800000;"><a href="https://www.jb51.cc/tag/s/" target="_blank" class="keywords">%s</a>.<a href="https://www.jb51.cc/tag/s/" target="_blank" class="keywords">%s</a></span><span style="color: #800000;">'</span> %<span style="color: #000000;"> (app_name,match_key)
</span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">perm str:</span><span style="color: #800000;">"</span><span style="color: #000000;">,perm_obj)
</span><span style="color: #0000ff;">if</span><span style="color: #000000;"> request.user.has_perm(perm_obj):
</span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">'</span><span style="color: #800000;">当前<a href="https://www.jb51.cc/tag/yonghu/" target="_blank" class="keywords">用户</a>有此权限</span><span style="color: #800000;">'</span><span style="color: #000000;">)
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> True
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
</span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">'</span><span style="color: #800000;">当前<a href="https://www.jb51.cc/tag/yonghu/" target="_blank" class="keywords">用户</a>没有该权限</span><span style="color: #800000;">'</span><span style="color: #000000;">)
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> False
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
</span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">未匹配到权限项,当前<a href="https://www.jb51.cc/tag/yonghu/" target="_blank" class="keywords">用户</a>无权限</span><span style="color: #800000;">"</span><span style="color: #000000;">)
<span style="color: #0000ff;">def<span style="color: #000000;"> check_permission(func):
<span style="color: #0000ff;">def inner(*args,<span style="color: #000000;">kwargs):
<span style="color: #0000ff;">if <span style="color: #0000ff;">not perm_check(*args,*<span style="color: #000000;">kwargs):
request =<span style="color: #000000;"> args[0]
<span style="color: #0000ff;">return render(request,<span style="color: #800000;">'<span style="color: #800000;">kingadmin/page_403.html<span style="color: #800000;">'<span style="color: #000000;">)
<span style="color: #0000ff;">return func(args,<span style="color: #000000;">kwargs)
<span style="color: #0000ff;">return inner
如果您也喜欢它,动动您的小指点个赞吧