前面的菜单、部门、职位与管理员管理功能完成后,接下来要处理的是将它们关联起来,根据职位管理中选定的权限控制菜单显示以及页面数据的访问和操作。

  那么要怎么改造呢?我们可以通过用户的操作步骤来一步步进行处理,具体思路如下:

  1.用户在管理端登录时,通过用户记录所绑定的职位信息,来确定用户所拥有的权限。我们可以在登录接口中,将该管理员的职位id存储到session中,以方便后续的调用。

  2.登录成功后,跳转进入管理界口,在获取菜单列表时,需要对菜单列表进行处理,只列出当前用户有权限的菜单项。

  3.在点击菜单进入相关数据页面或在数据页面进行增删改查等操作时,需要进行权限判断,判断是否有权限进行查看或操作。由于我们是前后端分离,所以权限只需要在接口进行处理。

  首先我们来简单改造一下登录接口login.py,只需要在将职位id存储到session中就可以了

  1. ##############################################################
  2. ### 把用户信息保存到session中 ###
  3. ##############################################################
  4. manager_id = manager_result.get('id', 0)
  5. s['id'] = manager_id
  6. s['login_name'] = username
  7. s['positions_id'] = manager_result.get('positions_id', '')
  8. s.save()

  找到上面内容,在里面插入 s['positions_id'] = manager_result.get('positions_id', '')

  接下来改造菜单列表接口menu_info.py文件的@get('/api/main/menu_info/')接口,我们需要做以下操作:

  1.首先从session中获取当前用户的职位id,然后根据职位id从职位表中读取对应的权限数据

  2.其次在菜单的遍历组装过程中,添加判断用户的权限,没有权限的菜单项直接过滤掉

  1. @get('/api/main/menu_info/')
  2. def callback():
  3. """
  4. 主页面获取菜单列表数据
  5. """
  6. # 获取当前用户权限
  7. session = web_helper.get_session()
  8. if session:
  9. _positions_logic = positions_logic.PositionsLogic()
  10. page_power = _positions_logic.get_page_power(session.get('positions_id'))
  11. else:
  12. page_power = ''
  13. if not page_power:
  14. return web_helper.return_msg(-404, '您的登录已超时,请重新登录')
  15.  
  16. _menu_info_logic = menu_info_logic.MenuInfoLogic()
  17. # 读取记录
  18. result = _menu_info_logic.get_list('*', 'is_show and is_enabled', orderby='sort')
  19. if result:
  20. # 定义最终输出的html存储变量
  21. html = ''
  22. for model in result.get('rows'):
  23. # 检查是否有权限
  24. if ',' + str(model.get('id')) + ',' in page_power:
  25. # 提取出第一级菜单
  26. if model.get('parent_id') == 0:
  27. # 添加一级菜单
  28. temp = """
  29. <dl id="menu-%(id)s">
  30. <dt><i class="Hui-iconfont">%(icon)s</i> %(name)s<i class="Hui-iconfont menu_dropdown-arrow"></i></dt>
  31. <dd>
  32. <ul>
  33. """ % {'id': model.get('id'), 'icon': model.get('icon'), 'name': model.get('name')}
  34. html = html + temp
  35.  
  36. # 从所有菜单记录中提取当前一级菜单下的子菜单
  37. for sub_model in result.get('rows'):
  38. # 检查是否有权限
  39. if ',' + str(sub_model.get('id')) + ',' in page_power:
  40. # 如果父id等于当前一级菜单id,则为当前菜单的子菜单
  41. if sub_model.get('parent_id') == model.get('id'):
  42. temp = """
  43. <li><a data-href="%(page_url)s" data-title="%(name)s" href="javascript:void(0)">%(name)s</a></li>
  44. """ % {'page_url': sub_model.get('page_url'), 'name': sub_model.get('name')}
  45. html = html + temp
  46.  
  47. # 闭合菜单html
  48. temp = """
  49. </ul>
  50. </dd>
  51. </dl>
  52. """
  53. html = html + temp
  54.  
  55. return web_helper.return_msg(0, '成功', {'menu_html': html})
  56. else:
  57. 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()方法,来获取其值出来使用。

  1. def get_page_power(self, positions_id):
  2. """获取当前用户权限"""
  3. page_power = self.get_value_for_cache(positions_id, 'page_power')
  4. if page_power:
  5. return ',' + page_power + ','
  6. else:
  7. 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,与职位权限进行比较,判断当前用户是否拥有访问该接口的权限,如果有则跳过,没有则拒绝访问。

  具体代码如下:

  1. #!/usr/bin/env python
  2. # coding=utf-8
  3.  
  4. from bottle import request
  5. from common import web_helper
  6. from logic import menu_info_logic, positions_logic
  7.  
  8. def check_user_power():
  9. """检查当前用户是否有访问当前接口的权限"""
  10. # 获取当前页面原始路由
  11. rule = request.route.rule
  12. # 获取当前访问接口方式(get/post/put/delete)
  13. method = request.method.lower()
  14.  
  15. # 获取来路url
  16. http_referer = request.environ.get('HTTP_REFERER')
  17. if http_referer:
  18. # 提取页面url地址
  19. index = http_referer.find('?')
  20. if index == -1:
  21. url = http_referer[http_referer.find('/', 8) + 1:]
  22. else:
  23. url = http_referer[http_referer.find('/', 8) + 1: index]
  24. else:
  25. url = ''
  26.  
  27. # 组合当前接口访问的缓存key值
  28. key = url + method + '(' + rule + ')'
  29. # 从菜单权限缓存中读取对应的菜单实体
  30. menu_info = menu_info_logic.MenuInfoLogic()
  31. model = menu_info.get_model_for_url(key)
  32. if not model:
  33. web_helper.return_raise(web_helper.return_msg(-1, "您没有访问权限1" + key))
  34.  
  35. # 读取session
  36. session = web_helper.get_session()
  37. if session:
  38. # 从session中获取当前用户登录时所存储的职位id
  39. positions = positions_logic.PositionsLogic()
  40. page_power = positions.get_page_power(session.get('positions_id'))
  41. # 从菜单实体中提取菜单id,与职位权限进行比较,判断当前用户是否拥有访问该接口的权限
  42. if page_power.find(',' + str(model.get('id', -1)) + ',') == -1:
  43. web_helper.return_raise(web_helper.return_msg(-1, "您没有访问权限2"))
  44. else:
  45. web_helper.return_raise(web_helper.return_msg(-404, "您的登录已失效,请重新登录"))

  对于前面所讲的菜单权限缓存,下面详细讲解一下。

  由于菜单跟接口都很多,我们在做权限判断时,就需要在访问接口时,自动匹配找到该接口对应的菜单项,然后才可以根据菜单id和权限字符进行比较,判断是否拥有操作权限,而自动匹配这里如果直接通过数据库查找的话,操作会比较复杂,也会影响使用性能,所以我们可以通过将在菜单管理中注册的菜单项进行分解,按一定的规则组合生成对应的缓存key,存储到nosql中,当访问接口时,我们根据规则组合成对应的key直接在nosql中查找就可以实现我们想要的功能了。当然第一次访问或我们清除缓存后,这些key值是不存在的,所以我们可以加个判断,如果缓存不存在时,重新加载生成对应的key就可以了。

  具体代码如下:

  1. def get_model_for_url(self, key):
  2. """通过当前页面路由url,获取菜单对应的记录"""
  3. # 使用md5生成对应的缓存key值
  4. key_md5 = encrypt_helper.md5(key)
  5. # 从缓存中提取菜单记录
  6. model = cache_helper.get(key_md5)
  7. # 记录不存在时,运行记录载入缓存程序
  8. if not model:
  9. self._load_cache()
  10. model = cache_helper.get(key_md5)
  11. return model
  12.  
  13. def _load_cache(self):
  14. """全表记录载入缓存"""
  15. # 生成缓存载入状态key,主要用于检查是否已执行了菜单表载入缓存判断
  16. cache_key = self.__table_name + '_is_load'
  17. # 将自定义的key存储到全局缓存队列中(关于全局缓存队列请查看前面ORM对应章节说明)
  18. self.add_relevance_cache_in_list(cache_key)
  19. # 获取缓存载入状态,检查记录是否已载入缓存,是的话则不再执行
  20. if cache_helper.get(cache_key):
  21. return
  22. # 从数据库中读取全部记录
  23. result = self.get_list()
  24. # 标记记录已载入缓存
  25. cache_helper.set(cache_key, True)
  26. # 如果菜单表没有记录,则直接退出
  27. if not result:
  28. return
  29. # 循环遍历所有记录,组合处理后,存储到nosql缓存中
  30. for model in result.get('rows', {}):
  31. # 提取菜单页面对应的接口(后台菜单管理中的接口值,同一个菜单操作时,经常需要访问多个接口,所以这个值有中存储多们接口值)
  32. interface_url = model.get('interface_url', '')
  33. if not interface_url:
  34. continue
  35. # 获取前端html页面地址
  36. page_url = model.get('page_url', '')
  37.  
  38. # 同一页面接口可能有多个,所以需要进行分割
  39. interface_url_arr = interface_url.replace('\n', '').replace(' ', '').split(',')
  40. # 逐个接口处理
  41. for interface in interface_url_arr:
  42. # html+接口组合生成key
  43. url_md5 = encrypt_helper.md5(page_url + interface)
  44. # 存储到全局缓存队列中,方便菜单记录更改时,自动清除这些自定义缓存
  45. self.add_relevance_cache_in_list(url_md5)
  46. # 存储到nosql缓存
  47. cache_helper.set(url_md5, model)

  这里的权限管理逻辑有点绕,需要认真思考与debug检查,才能真正掌握。另外,也可以通过后台菜单管理中,故意修改菜单项的某些值,来检查这里的代码处理与变化。

  完成以上代码以后,权限的处理就完成了,接下来只需要在每个后台管理接口中添加下面代码就可以做到接口的访问权限控制了。

  1. @get('/api/main/menu_info/')
  2. def callback():
  3. """
  4. 主页面获取菜单列表数据
  5. """
  6. # 检查用户权限
  7. _common_logic.check_user_power()

  具体大家可以查看文章后面提供的源码,看看后台管理接口处理就清楚了。

  本文对应的源码下载 

版权声明:本文原创发表于 博客园,作者为 AllEmpty 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。

python开发QQ群:669058475(本群已满)、733466321(可以加2群)    作者博客:http://www.cnblogs.com/EmptyFS/

我的第一个python web开发框架(39)——后台接口权限访问控制处理的更多相关文章

  1. 我的第一个python web开发框架(35)——权限数据库结构设计

    接下来要做的是权限系统的数据库结构设计,在上一章我们了解了权限系统是通过什么来管理好权限的,我们选用其中比较常用的权限系统来实现当前项目管理要求. 下面是我们选择的权限系统关系模型: 从以上关系可以看 ...

  2. 我的第一个python web开发框架(41)——总结

    我的第一个python web开发框架系列博文从17年6.7月份开始写(存了近十章稿留到9月份才开始发布),到今天结束,一年多时间,想想真不容易啊. 整个过程断断续续,中间有段时间由于工作繁忙停了好长 ...

  3. 我的第一个python web开发框架(14)——后台管理系统登录功能

    接下来正式进入网站的功能开发.要完成后台管理系统登录功能,通过查看登录页面,我们可以了解到,我们需要编写验证码图片获取接口和登录处理接口,然后在登录页面的HTML上编写AJAX. 在进行接口开发之前, ...

  4. 我的第一个python web开发框架(21)——小结

    这个小网站终于成功上线,小白除了收获一笔不多的费用外,还得到女神小美的赞赏,心中满满的成就感.这一天下班后,他请老菜一起下馆子,兑现请吃饭的承诺,顺便让老菜点评一下. 小白:老大,在你的指导下终于完成 ...

  5. 我的第一个python web开发框架(1)——前言

    由于之前经验不是很丰富,写的C#系统太过复杂,所以一直想重写,但学的越多越觉得自己懂的越少,越觉的底气不足.所以一直不敢动手,在内心深处对自己讲,要静下心来认真学习,继续沉淀沉淀.这两年多以来找各种机 ...

  6. 我的第一个python web开发框架(3)——怎么开始?

    小白与小美公司经过几次接触商谈,好不容易将外包签订了下来,准备开始大干一场.不过小白由于没有太多的项目经验,学过python懂得python的基本语法,在公司跟着大家做过简单功能,另外还会一些HTML ...

  7. 我的第一个python web开发框架(22)——一个安全小事故

    在周末的一个早上,小白还在做着美梦,就收到了小美的连环追魂call,电话一直响个不停. 小白打着哈欠拿起电话:早上好美女. 小美:出事了出事了,我们公司网站一早访问是一片空白,什么内容都没有了,你赶急 ...

  8. 我的第一个python web开发框架(15)——公司介绍编辑功能

    完成登录以后,就会进入后台管理系统的主界面,因为这个是小项目,所以导航菜单全部固化在HTML中,不能修改.一般后台还会有一个欢迎页或关键数据展示的主页面,小项目也没有多大的必要,所以登录后直接进入公司 ...

  9. 我的第一个python web开发框架(40)——后台日志与异常处理

    后台权限和底层框架的改造终于完成了,小白也终于可以放下紧悬着的心,可以轻松一下了.这不他为了感谢老菜,又找老菜聊了起来. 小白:多谢老大的帮忙,系统终于改造完成了,可以好好放松一下了. 老菜:呵呵,对 ...

随机推荐

  1. 为什么MIP-Cache存在

    在去年十月的一篇 博客文章 提到 MIP 加速器是由 MIP-HTML, MIP-JS 和 MIP-Cache 三个模块共同产生加速效果的.MIP-HTML 和 MIP-JS 都是开源的,在 GitH ...

  2. Kali Linux 渗透测试手册(1.1)安装虚拟机

    翻译来自:掣雷小组 成员信息: thr0cyte, Gr33k, 花花, 小丑, R1ght0us, 7089bAt, 一.配置KALI Linux和渗透测试环境 在这一章,我们将覆盖以下内容: 在W ...

  3. MySQL 查询出的时间相差几个小时

    最近做的一个springboot2.0项目. 前提是我的服务器时区没有问题: [root@wangbo ~]# date -R Mon, 22 Apr 2019 19:24:33 +0800 可以参考 ...

  4. 仓储repository概念

    1.为什么要用仓储?(仓储有什么用) 1.1 解耦 为了解耦领域层与数据映射层的关系. 1.2 管理增删查改 仓储模式最大的优点就是所有的数据访问首先是通过仓库的,对仓库的增删改都不会立即提交到数据库 ...

  5. Spring Cloud Eureka 注册中心集群搭建,Greenwich 最新版!

    Spring Cloud 的注册中心可以由 Eureka.Consul.Zookeeper.ETCD 等来实现,这里推荐使用 Spring Cloud Eureka 来实现注册中心,它基于 Netfl ...

  6. Java实现单链表

    真正的动态数据结构(引用和指针) 优点:真正的动态,不需要处理固定容量的问题. 缺点:丧失随机访问的能力. 链表就像寻宝,我们拿到藏宝图开始出发寻宝,每找到一个地方后,里面藏着下一步应该去哪里寻找.一 ...

  7. python接口自动化(四)--接口测试工具介绍(详解)

    简介 “工欲善其事必先利其器”,通过前边几篇文章的介绍,大家大致对接口有了进一步的认识.那么接下来让我们看看接口测试的工具有哪些. 目前,市场上有很多支持接口测试的工具.利用工具进行接口测试,能够提供 ...

  8. [开源]Entity Framework 6 Repository 一种实现方式

    在使用Entity Framework这种ORM框架得时候,一般结合Repository仓储形式来处理业务逻辑:虽然这种模式带来很多好处,但是也会引发一些争议,在此抛开不谈,小弟结合项目经验来实现一下 ...

  9. 机器学习之logistic回归算法与代码实现原理

    Logistic回归算法原理与代码实现 本文系作者原创,转载请注明出处:https://www.cnblogs.com/further-further-further/p/10033567.html ...

  10. 【我们一起写框架】MVVM的WPF框架(一)—序篇

    前言 我想,有一部分程序员应该是在二三线城市的,虽然不知道占比,但想来应该不在少数. 我是这部分人群中的一份子. 我们这群人,面对的客户,大多是国内中小企业,或者政府的小部门.这类客户的特点是,资金有 ...