我的第一个python web开发框架(39)——后台接口权限访问控制处理
前面的菜单、部门、职位与管理员管理功能完成后,接下来要处理的是将它们关联起来,根据职位管理中选定的权限控制菜单显示以及页面数据的访问和操作。
那么要怎么改造呢?我们可以通过用户的操作步骤来一步步进行处理,具体思路如下:
1.用户在管理端登录时,通过用户记录所绑定的职位信息,来确定用户所拥有的权限。我们可以在登录接口中,将该管理员的职位id存储到session中,以方便后续的调用。
2.登录成功后,跳转进入管理界口,在获取菜单列表时,需要对菜单列表进行处理,只列出当前用户有权限的菜单项。
3.在点击菜单进入相关数据页面或在数据页面进行增删改查等操作时,需要进行权限判断,判断是否有权限进行查看或操作。由于我们是前后端分离,所以权限只需要在接口进行处理。
首先我们来简单改造一下登录接口login.py,只需要在将职位id存储到session中就可以了
##############################################################
### 把用户信息保存到session中 ###
##############################################################
manager_id = manager_result.get('id', 0)
s['id'] = manager_id
s['login_name'] = username
s['positions_id'] = manager_result.get('positions_id', '')
s.save()
找到上面内容,在里面插入 s['positions_id'] = manager_result.get('positions_id', '')
接下来改造菜单列表接口menu_info.py文件的@get('/api/main/menu_info/')接口,我们需要做以下操作:
1.首先从session中获取当前用户的职位id,然后根据职位id从职位表中读取对应的权限数据
2.其次在菜单的遍历组装过程中,添加判断用户的权限,没有权限的菜单项直接过滤掉
@get('/api/main/menu_info/')
def callback():
"""
主页面获取菜单列表数据
"""
# 获取当前用户权限
session = web_helper.get_session()
if session:
_positions_logic = positions_logic.PositionsLogic()
page_power = _positions_logic.get_page_power(session.get('positions_id'))
else:
page_power = ''
if not page_power:
return web_helper.return_msg(-404, '您的登录已超时,请重新登录') _menu_info_logic = menu_info_logic.MenuInfoLogic()
# 读取记录
result = _menu_info_logic.get_list('*', 'is_show and is_enabled', orderby='sort')
if result:
# 定义最终输出的html存储变量
html = ''
for model in result.get('rows'):
# 检查是否有权限
if ',' + str(model.get('id')) + ',' in page_power:
# 提取出第一级菜单
if model.get('parent_id') == 0:
# 添加一级菜单
temp = """
<dl id="menu-%(id)s">
<dt><i class="Hui-iconfont">%(icon)s</i> %(name)s<i class="Hui-iconfont menu_dropdown-arrow"></i></dt>
<dd>
<ul>
""" % {'id': model.get('id'), 'icon': model.get('icon'), 'name': model.get('name')}
html = html + temp # 从所有菜单记录中提取当前一级菜单下的子菜单
for sub_model in result.get('rows'):
# 检查是否有权限
if ',' + str(sub_model.get('id')) + ',' in page_power:
# 如果父id等于当前一级菜单id,则为当前菜单的子菜单
if sub_model.get('parent_id') == model.get('id'):
temp = """
<li><a data-href="%(page_url)s" data-title="%(name)s" href="javascript:void(0)">%(name)s</a></li>
""" % {'page_url': sub_model.get('page_url'), 'name': sub_model.get('name')}
html = html + temp # 闭合菜单html
temp = """
</ul>
</dd>
</dl>
"""
html = html + temp return web_helper.return_msg(0, '成功', {'menu_html': html})
else:
return web_helper.return_msg(-1, "查询失败")
第9与第10行,就是从职位表中,读取指定职位id的权限page_power字段值,第24行与第39行中,只需要判断当前菜单id是否存在page_power字段值中,就可以判断是否拥有该菜单权限了,因为在前面职位管理那里,勾选了指定菜单id后,就会将菜单的id存储到这个字段中。
由于可能多处需要读取权限page_power字段值,这里我们需要在职位逻辑类positions_logic.py中添加get_page_power()方法,来获取其值出来使用。
def get_page_power(self, positions_id):
"""获取当前用户权限"""
page_power = self.get_value_for_cache(positions_id, 'page_power')
if page_power:
return ',' + page_power + ','
else:
return ','
我们调用ORM的get_value_for_cache()方法,直接通过主键id来读取我们想要的字段值,并在权限字串两端添加逗号,因为我们在比较菜单id是否存在于权限字串时,不加上逗号可能会出错,比如说权限串有2,10,11,如果我们直接比较1是否存在于权限串中,如果不转为list,直接字符串比较,返回结果就会为True,因为10和11都存在1,而各增加逗号以后比较就不一样了,,2,10,11,与,1,比较肯定返回的是False,也就是说当前管理员没有拥有1这个菜单id的权限。
PS:完成菜单列表功能的改造后,记得检查菜单列表页面(main.html)和改造的接口是否在上一章节结束后,添加到菜单管理项中,并在职位管理中将对应的权限项打上勾,如果没有的话,完成本文改造,登录后台将会提示你没有访问权限。
最后要处理的是后台管理各接口的权限判断,由于bottle勾子(@hook('before_request'))直接获取当前访问的路由(接口),所获取到的都有具体值(比如:@get('/system/menu_info/<id:int>/') 这个路由,在勾子中取到的是/system/menu_info/1/, 由于id值是不固定的,我们要处理起来会很麻),所以我们只能在每个接口中直接处理,也就是说我们需要在每个接口中,添加固定的权限判断方法调用。
而权限的处理需要对数据库对数据库进行读取操作,所以我们可以在逻辑层文件夹中(logic)添加一个通用的逻辑层模块_common_logic.py,将权限判断方法在这个文件中实现,方便调用。
这里的权限判断实现原理是:通过获取web来路html页面名称、当前接口访问方式(method)、当前访问的接口路由名称,将它们组成一个key值,从菜单权限初始化缓存中读取出对应的菜单实体(后面会讲到如何生成这个菜单权限缓存),提取当前所访问接口所对应的菜单id值,然后通过从session中获取当前用户的职位id,获取当前用户所拥有的职位权限,将菜单id与职位权限进行比较,判断用户是否拥有当前所访问的接口权限,从而达到对权限的访问控制。
具体实现这个权限判断方法,有以下步骤:
1.首先我们需要获取web的来路地址HTTP_REFERER,由于我们在前面菜单管理中,录入的html页面地址不包括域名和参数,所以来路地址需要去掉当前域名和?号后面的附加参数,只保留html页面名称。
2.直接从从bottle的request中,读取当前访问接口的路由值(rule)
3.从bottle的request中获取当前访问接口的方式(get/post/put/delete)
4.将前面三步获取的值组合成菜单对应的唯一key,然后在菜单权限缓存中读取对应的菜单实体
5.如果菜单记录实体不存在,则表达当前接口未注册或注册时所提交的信息错误,当前用户没有该接口的访问权限
6.从session中获取当前用户登录时所存储的职位id,然后通过该id读取对应的职位权限
7.从菜单实体中提取菜单id,与职位权限进行比较,判断当前用户是否拥有访问该接口的权限,如果有则跳过,没有则拒绝访问。
具体代码如下:
#!/usr/bin/env python
# coding=utf-8 from bottle import request
from common import web_helper
from logic import menu_info_logic, positions_logic def check_user_power():
"""检查当前用户是否有访问当前接口的权限"""
# 获取当前页面原始路由
rule = request.route.rule
# 获取当前访问接口方式(get/post/put/delete)
method = request.method.lower() # 获取来路url
http_referer = request.environ.get('HTTP_REFERER')
if http_referer:
# 提取页面url地址
index = http_referer.find('?')
if index == -1:
url = http_referer[http_referer.find('/', 8) + 1:]
else:
url = http_referer[http_referer.find('/', 8) + 1: index]
else:
url = '' # 组合当前接口访问的缓存key值
key = url + method + '(' + rule + ')'
# 从菜单权限缓存中读取对应的菜单实体
menu_info = menu_info_logic.MenuInfoLogic()
model = menu_info.get_model_for_url(key)
if not model:
web_helper.return_raise(web_helper.return_msg(-1, "您没有访问权限1" + key)) # 读取session
session = web_helper.get_session()
if session:
# 从session中获取当前用户登录时所存储的职位id
positions = positions_logic.PositionsLogic()
page_power = positions.get_page_power(session.get('positions_id'))
# 从菜单实体中提取菜单id,与职位权限进行比较,判断当前用户是否拥有访问该接口的权限
if page_power.find(',' + str(model.get('id', -1)) + ',') == -1:
web_helper.return_raise(web_helper.return_msg(-1, "您没有访问权限2"))
else:
web_helper.return_raise(web_helper.return_msg(-404, "您的登录已失效,请重新登录"))
对于前面所讲的菜单权限缓存,下面详细讲解一下。
由于菜单跟接口都很多,我们在做权限判断时,就需要在访问接口时,自动匹配找到该接口对应的菜单项,然后才可以根据菜单id和权限字符进行比较,判断是否拥有操作权限,而自动匹配这里如果直接通过数据库查找的话,操作会比较复杂,也会影响使用性能,所以我们可以通过将在菜单管理中注册的菜单项进行分解,按一定的规则组合生成对应的缓存key,存储到nosql中,当访问接口时,我们根据规则组合成对应的key直接在nosql中查找就可以实现我们想要的功能了。当然第一次访问或我们清除缓存后,这些key值是不存在的,所以我们可以加个判断,如果缓存不存在时,重新加载生成对应的key就可以了。
具体代码如下:
def get_model_for_url(self, key):
"""通过当前页面路由url,获取菜单对应的记录"""
# 使用md5生成对应的缓存key值
key_md5 = encrypt_helper.md5(key)
# 从缓存中提取菜单记录
model = cache_helper.get(key_md5)
# 记录不存在时,运行记录载入缓存程序
if not model:
self._load_cache()
model = cache_helper.get(key_md5)
return model def _load_cache(self):
"""全表记录载入缓存"""
# 生成缓存载入状态key,主要用于检查是否已执行了菜单表载入缓存判断
cache_key = self.__table_name + '_is_load'
# 将自定义的key存储到全局缓存队列中(关于全局缓存队列请查看前面ORM对应章节说明)
self.add_relevance_cache_in_list(cache_key)
# 获取缓存载入状态,检查记录是否已载入缓存,是的话则不再执行
if cache_helper.get(cache_key):
return
# 从数据库中读取全部记录
result = self.get_list()
# 标记记录已载入缓存
cache_helper.set(cache_key, True)
# 如果菜单表没有记录,则直接退出
if not result:
return
# 循环遍历所有记录,组合处理后,存储到nosql缓存中
for model in result.get('rows', {}):
# 提取菜单页面对应的接口(后台菜单管理中的接口值,同一个菜单操作时,经常需要访问多个接口,所以这个值有中存储多们接口值)
interface_url = model.get('interface_url', '')
if not interface_url:
continue
# 获取前端html页面地址
page_url = model.get('page_url', '') # 同一页面接口可能有多个,所以需要进行分割
interface_url_arr = interface_url.replace('\n', '').replace(' ', '').split(',')
# 逐个接口处理
for interface in interface_url_arr:
# html+接口组合生成key
url_md5 = encrypt_helper.md5(page_url + interface)
# 存储到全局缓存队列中,方便菜单记录更改时,自动清除这些自定义缓存
self.add_relevance_cache_in_list(url_md5)
# 存储到nosql缓存
cache_helper.set(url_md5, model)
这里的权限管理逻辑有点绕,需要认真思考与debug检查,才能真正掌握。另外,也可以通过后台菜单管理中,故意修改菜单项的某些值,来检查这里的代码处理与变化。
完成以上代码以后,权限的处理就完成了,接下来只需要在每个后台管理接口中添加下面代码就可以做到接口的访问权限控制了。
@get('/api/main/menu_info/')
def callback():
"""
主页面获取菜单列表数据
"""
# 检查用户权限
_common_logic.check_user_power()
具体大家可以查看文章后面提供的源码,看看后台管理接口处理就清楚了。
版权声明:本文原创发表于 博客园,作者为 AllEmpty 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
python开发QQ群:669058475(本群已满)、733466321(可以加2群) 作者博客:http://www.cnblogs.com/EmptyFS/
我的第一个python web开发框架(39)——后台接口权限访问控制处理的更多相关文章
- 我的第一个python web开发框架(35)——权限数据库结构设计
接下来要做的是权限系统的数据库结构设计,在上一章我们了解了权限系统是通过什么来管理好权限的,我们选用其中比较常用的权限系统来实现当前项目管理要求. 下面是我们选择的权限系统关系模型: 从以上关系可以看 ...
- 我的第一个python web开发框架(41)——总结
我的第一个python web开发框架系列博文从17年6.7月份开始写(存了近十章稿留到9月份才开始发布),到今天结束,一年多时间,想想真不容易啊. 整个过程断断续续,中间有段时间由于工作繁忙停了好长 ...
- 我的第一个python web开发框架(14)——后台管理系统登录功能
接下来正式进入网站的功能开发.要完成后台管理系统登录功能,通过查看登录页面,我们可以了解到,我们需要编写验证码图片获取接口和登录处理接口,然后在登录页面的HTML上编写AJAX. 在进行接口开发之前, ...
- 我的第一个python web开发框架(21)——小结
这个小网站终于成功上线,小白除了收获一笔不多的费用外,还得到女神小美的赞赏,心中满满的成就感.这一天下班后,他请老菜一起下馆子,兑现请吃饭的承诺,顺便让老菜点评一下. 小白:老大,在你的指导下终于完成 ...
- 我的第一个python web开发框架(1)——前言
由于之前经验不是很丰富,写的C#系统太过复杂,所以一直想重写,但学的越多越觉得自己懂的越少,越觉的底气不足.所以一直不敢动手,在内心深处对自己讲,要静下心来认真学习,继续沉淀沉淀.这两年多以来找各种机 ...
- 我的第一个python web开发框架(3)——怎么开始?
小白与小美公司经过几次接触商谈,好不容易将外包签订了下来,准备开始大干一场.不过小白由于没有太多的项目经验,学过python懂得python的基本语法,在公司跟着大家做过简单功能,另外还会一些HTML ...
- 我的第一个python web开发框架(22)——一个安全小事故
在周末的一个早上,小白还在做着美梦,就收到了小美的连环追魂call,电话一直响个不停. 小白打着哈欠拿起电话:早上好美女. 小美:出事了出事了,我们公司网站一早访问是一片空白,什么内容都没有了,你赶急 ...
- 我的第一个python web开发框架(15)——公司介绍编辑功能
完成登录以后,就会进入后台管理系统的主界面,因为这个是小项目,所以导航菜单全部固化在HTML中,不能修改.一般后台还会有一个欢迎页或关键数据展示的主页面,小项目也没有多大的必要,所以登录后直接进入公司 ...
- 我的第一个python web开发框架(40)——后台日志与异常处理
后台权限和底层框架的改造终于完成了,小白也终于可以放下紧悬着的心,可以轻松一下了.这不他为了感谢老菜,又找老菜聊了起来. 小白:多谢老大的帮忙,系统终于改造完成了,可以好好放松一下了. 老菜:呵呵,对 ...
随机推荐
- java中用swing做一个windows计算器
目录 主函数 普通计数器 科学计算器 注意: @(java中用swing做一个windows计算器) 前言: 来看这篇教程估计都是java课程设计吧,现在已经没有公司很少使用swing组件了,java ...
- 章节十、7-Xpath---Xpath中绝对路径相对路径的区别
以下演示操作以该网址中的内容为例:https://learn.letskodeit.com/?_ga=2.143454972.85111248.1555037144-697706367.1554889 ...
- git工具使用说明
一.什么是git? Git是分布式版本控制系统 概念: 工作区:就是你在电脑里能看到的目录: 暂存区:一般存放在(.git/index)中,所以我们把暂存区有时也叫作索引(index ...
- 博客系统typecho的安装与使用
之前用过wordpress和emlog的博客系统,感觉上wordpress功能强大,插件多,而且也不局限博客网站,就是资源占用比较高,emlog比较简单,资源占用少,就是界面不太喜欢,功能相对也少了些 ...
- LDAP概念和原理介绍
LDAP概念和原理介绍 相信对于许多的朋友来说,可能听说过LDAP,但是实际中对LDAP的了解和具体的原理可能还比较模糊,今天就从“什么是LDAP”.“LDAP的主要产品”.“LDAP的基本模型”.“ ...
- perl学习笔记--搭建开发环境
windows下perl开发环境搭建 perl下载地址:http://www.activestate.com/developer-tools 各个插件的安装方法:(通过代理上网的方法) 方法一:pad ...
- 【重学计算机】机组D7章:总线
1. 系统总线的特性及应用 总线概念:将计算机系统中各部件连接起来 总线分类:(外部/内部,系统/非系统,串行/并行,同步/异步...) 按用途分类: 存储总线:cpu与存储器 系统总线:连接存储总线 ...
- RecyclerFlexboxLayoutManagerDemo【使用FlexboxLayoutManager实现流式布局】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 FlexboxLayout是一个Google 开源的库项目,它将CSS Flexible Box Layout Module的类似功 ...
- .NET Core 控制台应用程序使用异步(Async)Main方法
C# 7.1 及以上的版本允许我们使用异步的Main方法. 一.新建一个控制台应用程序 二.异步Main方法 我们直接将Main方法改为如下: static async Task Main(strin ...
- 并发系列(5)之 Future 框架详解
本文将主要讲解 J.U.C 中的 Future 框架,并分析结合源码分析其内部结构逻辑: 一.Future 框架概述 JDK 中的 Future 框架实际就是 Future 模式的实现,通常情况下我们 ...