权限管理rbac:基于角色的权限控制

权限管理rbac:基于角色的权限控制

 

 

        table相关

table的设计

 1 from django.db import models
 2 
 3 class UserInfo(models.Model):
 4     username = models.CharField(max_length=40)
 5     password = models.CharField(max_length=40)
 6     email = models.EmailField(null=True, blank=True)
 7     nickname = models.CharField(max_length=40, null=True, blank=True)
 8     role = models.ManyToManyField("Role")
 9 
10     def __str__(self):
11         return self.username
12 
13 
14 class Role(models.Model):
15     name = models.CharField(max_length=32)
16     authority = models.ManyToManyField("Authority")
17 
18     def __str__(self):
19         return self.name
20 
21 
22 class Authority(models.Model):
23     title = models.CharField(max_length=32)
24     url = models.CharField(max_length=100)
25     menu = models.ForeignKey("Menu", null=True, blank=True)
26 
27     def __str__(self):
28         return self.title
29 
30 
31 class Menu(models.Model):
32     title = models.CharField(max_length=32)
33     pid = models.ForeignKey("self", null=True, blank=True)
34 
35     def __str__(self):
36         menu_title_list = [self.title]
37         pid = self.pid
38         while pid:
39             menu_title_list.insert(0, pid.title)
40             pid = pid.pid
41         return "-".join(menu_title_list)
创建 models

主要使用以下几张表:

  • UserInfo  
    • 用户名/密码/邮箱等基本信息
    • 与 Role 多对多的关系
  • Role
    • 角色的名字(相当于用户组)
    • 与 Authority 多对多的关系
  • Authority
    • 权限的名称 -- 用于显示
    • 权限的路径 -- 用于访问
    • 创建 Menu 的外键
  • Menu
    • 菜单的名称
    • 菜单的父级 id
  • role_authority
    • 由多对多创建,存放 Role 的id, Authority 的id
  • userinfo_role
    • 由多对多创建, 存放Userinfo 的id, Role 的id

table注册

导入 admin 模块以及存放定义了表格的 类,通过 admin.site.register( )注册

1 from django.contrib import admin
2 from . import models
3 
4 admin.site.register(models.UserInfo)
5 admin.site.register(models.Role)
6 admin.site.register(models.Authority)
7 admin.site.register(models.Menu)
注册 models

 

 

        登陆后的设置

在登陆成功以后,该用户的权限路径/菜单等存入 request.session 中

 1 from .. import models
 2 from django.conf import settings          # django 中有默认的配置以及自定义的配置,会在这里拿到合并后的结果
 3 
 4 def login_init(request, user_obj):        # 由于需要使用到 session 以及 登陆的对象,所以需要调用者传入
 5     authority_list = user_obj.role.values("authority__title",
 6                                           "authority__url",
 7                                           "authority__menu_id").distinct()
 8     url_list = []
 9     url_menu_list = []
10     for item in authority_list:
11         url_list.append(item["authority__url"])             # 将所有的权限 url 放入到 url_list,最后存入到 session 中;
12         if item["authority__menu_id"]:                      # 将拥有 菜单id 的权限的这条记录放入到 url_menu_list,最后存入到 session 中;
13             url_menu_list.append({
14                 "title": item["authority__title"],
15                 "url": item["authority__url"],
16                 "menu_id": item["authority__menu_id"],
17             })
18     menu_list = list(models.Menu.objects.values("id", "title", "pid"))         # 因为菜单相对的会比较小,所以将所有的菜单信息加入到 menu_list 中,最后存入到 session 中;
19 
20     request.session[settings.PERMISSION.get("session_persission_list")] = url_list
21     request.session[settings.PERMISSION.get("session_persission_menu_list")] = {
22         "url_menu_list": url_menu_list,
23         "menu_list": menu_list,
24     }
初始化登录后的 session

 

 

        中间件设置

在实际过程中,如果该用户访问的 url 不在自己的权限之内,那么应该直接返回,不需要进入到试图函数等流程中,所以,我们可以在中间件中的 request 中进行判断,由于需要使用到 session,所以,自定义的中间件应该放在最后的位置

 1 MIDDLEWARE = [
 2     'django.middleware.security.SecurityMiddleware',
 3     'django.contrib.sessions.middleware.SessionMiddleware',
 4     'django.middleware.common.CommonMiddleware',
 5     'django.middleware.csrf.CsrfViewMiddleware',
 6     'django.contrib.auth.middleware.AuthenticationMiddleware',
 7     'django.contrib.messages.middleware.MessageMiddleware',
 8     'django.middleware.clickjacking.XFrameOptionsMiddleware',
 9     'rbac.middleware.filterPermission.FilterPermission',            # 自定义的中间件
10 ]
中间件注册

中间件注册时,自定义的 class 应该继承与 MiddlewareMixin,导入方式为:from django.utils.deprecation import MiddlewareMixin 如果它不存在了,可以手写一个,然后继承,代码如下:

 1 class MiddlewareMixin(object):
 2     def __init__(self, get_response=None):
 3         self.get_response = get_response
 4         super(MiddlewareMixin, self).__init__()
 5 
 6     def __call__(self, request):
 7         response = None
 8         if hasattr(self, 'process_request'):
 9             response = self.process_request(request)
10         if not response:
11             response = self.get_response(request)
12         if hasattr(self, 'process_response'):
13             response = self.process_response(request, response)
14         return response
MiddlewareMixin 代码

继承完成后,便可以进行自定义的中间件插件的编写了

 1 import re
 2 from django.utils.deprecation import MiddlewareMixin
 3 from django.conf import settings
 4 from django.shortcuts import redirect
 5 
 6 
 7 class FilterPermission(MiddlewareMixin):
 8     """
 9     中间件,用来过滤权限,在
10     setting 中设置
11         rule:规定路径为开头结尾,用来拼接 url,格式为: "^{0}$"
12         session_persission_list: 存放 session 中权限路径的 key
13         safe_url:存放安全路径,登录前后都可以进入的列表,如:登录页面,注册页面等
14     """
15     def process_request(self, request):
16         permission_url = request.session.get(settings.PERMISSION.get("session_persission_list"))
17         current_url = request.path_info
18         rule = settings.PERMISSION.get("rule")
19 
20         for url in settings.PERMISSION.get("safe_url"):
21             if re.match(rule.format(url), current_url):
22                 return None
23 
24         for url in permission_url:
25             if re.match(rule.format(url), current_url):
26                 return None
27 
28         return redirect("/login.html")
自定义中间件创建

这样一来,我们便可以自动过滤/拦截没有权限的访问了

 

        自定义过滤器

 上面已经完成了中间件拦截非法路径的问题,下面我们需要对我们写的权限管理系统中的数据进行管理,那么,我们可以在页面做出一个菜单进行管理(多级菜单),这里我们通过 自定义过滤器 的方式实现

效果图,很粗糙

过滤器定义

1. 自定义的过滤器放到 templatetags 目录下

2. 引入 from django.template import Library

3. 实例化 register = Library( )

4. 在函数上加装饰器 (这里只用到了 simple_tag 一种) @register.simple_tag

  1 import re
  2 import os
  3 from django.conf import settings
  4 from django.template import Library
  5 from django.utils.safestring import mark_safe
  6 
  7 register = Library()
  8 
  9 
 10 
 11 
 12 def get_menus_dict(request):
 13     url_menu_list = request.session[settings.PERMISSION["session_persission_menu_list"]]["url_menu_list"]
 14     menu_list = request.session[settings.PERMISSION["session_persission_menu_list"]]["menu_list"]
 15 
 16     ret = []
 17     menu_dict = {}
 18     current_path = request.path_info
 19     # url_menu_list = [{'menu_id': 4, 'url': '/rbac/user/', 'title': '用户管理'}]
 20     # menu_list = [{'id': 1, 'title': '用户管理', 'pid': None}]
 21     for item in menu_list:
 22         item["children"] = []
 23         item["open"] = False
 24         item["status"] = False
 25         menu_dict[item["id"]] = item
 26 
 27     for item in url_menu_list:
 28         flag = False
 29 
 30         pid = item["menu_id"]
 31         menu_dict[pid]["children"].append(item)
 32         if re.match(settings.PERMISSION['rule'].format(item["url"]), current_path):
 33             flag = True
 34             item["status"] = True
 35         else:
 36             item["status"] = False
 37 
 38         while pid:
 39             menu_dict[pid]["status"] = True
 40             if flag:
 41                 menu_dict[pid]["open"] = True
 42             pid = menu_dict[pid]["pid"]
 43 
 44     for k, v in menu_dict.items():
 45         # {'status': True, 'children': [], 'id': 1, 'title': '用户管理', 'pid': None, 'open': False}
 46         if not v["pid"]:
 47             ret.append(v)
 48         else:
 49             menu_dict[v["pid"]]["children"].append(v)
 50 
 51     return ret
 52 
 53 
 54 def get_menus_html(menu_list):
 55     tpl1 = """
 56             <div class='rbac-menu-item'>
 57                 <div class='rbac-menu-header'>{0}</div>
 58                 <div class='rbac-menu-body {2}'>{1}</div>
 59             </div>
 60         """             # 父级别标签, {0} 菜单名字  {1} 子菜单信息   {2} class名称,用来确当是否展开这个菜单
 61 
 62 
 63     tpl2 = """
 64                     <a href='{0}' class='{1}'>{2}</a>
 65         """             # 权限标签 {0} 权限路径   {2} 权限名称
 66 
 67     html = ''
 68     for item in menu_list:
 69         if item.get('url', None):
 70             html += tpl2.format(item['url'], 'rbac_active' if item.get('status') else '', item.get('title'))
 71         elif not item.get('status'):
 72             continue
 73         else:
 74             html += tpl1.format(item['title'], get_menus_html(item['children']), '' if item.get('open') else 'rbac-hide')
 75 
 76     return html
 77 
 78 
 79 @register.simple_tag
 80 def rbac_menus(request):
 81     # 将 request.session 中的数据取出来处理成列表中包含字典的形式
 82     # 用作构建 标签 发送给前端
 83     menu_dict = get_menus_dict(request)
 84     # 用上面的数据,用来构建标签
 85     html = get_menus_html(menu_dict)
 86 
 87 
 88 
 89     return mark_safe(html)
 90 
 91 
 92 @register.simple_tag
 93 def rbac_css():         # 读取css代码,放入到前端中
 94     file_path = os.path.join('rbac', 'theme', 'rbac.css')
 95     if os.path.exists(file_path):
 96         return mark_safe(open(file_path, 'r', encoding='utf-8').read())
 97     else:
 98         raise Exception('rbac主题CSS文件不存在')
 99 
100 
101 @register.simple_tag
102 def rbac_js():         # 读取js代码,放入到前端中
103     file_path = os.path.join('rbac', 'theme', 'rbac.js')
104     if os.path.exists(file_path):
105         return mark_safe(open(file_path, 'r', encoding='utf-8').read())
106     else:
107         raise Exception('rbac主题JavaScript文件不存在')
完整过滤器代码

拼接后的字符串放入到前端以后,需要使用 safe 过滤器才能进行渲染,但是在自定义过滤器这里,似乎不太方便,这里使用一个新的方法

1. 引入 from django.utils.safestring import mark_safe

2. 使用 mark_safe(html)

from django.utils.safestring import mark_safe

mark_safe(html)

  

前端使用

下面是使用上面过滤器的页面

 1 {% load rbac %}
 2 {% load static %}
 3 
 4 <!DOCTYPE html>
 5 <html lang="en">
 6 <head>
 7     <meta charset="UTF-8">
 8     <meta http-equiv="x-ua-compatible" content="IE=edge">
 9     <meta name="viewport" content="width=device-width, initial-scale=1">
10     <title>Title</title>
11     <style>
12         .head{
13             height: 44px;
14             background-color: #369;
15         }
16 
17         .left{
18             float:left;
19             width: 200px;
20             background-color: azure;
21         }
22 
23         .right{
24             margin-left:200px;
25             background-color: beige;
26         }
27     </style>
28     <style>
29         {% rbac_css %}
30     </style>
31     <script src="{% static 'rbac/js/jquery-1.12.4.js' %}"></script>
32 </head>
33 <body>
34     <div class="head"></div>
35     <div class="body">
36         <div class="left">
37             {% rbac_menus request %}
38         </div>
39         <div class="right"></div>
40     </div>
41     <div class="foot"></div>
42 </body>
43 <script>
44     {% rbac_js %}
45 </script>
46 </html>
前端页面代码

由于我在设置编辑的时候,左面的菜单是固定不变的,所以这里用到了一个 母版的继承

1. 定义 母版 的时候,需要自定义的位置使用 {% block 名字%}{% endblock %} 来定义

2. 使用的时候,在 html 文件的开头使用 {% extends "母版路径" %} 来申明,使用时重写  {% block 名字%}{% endblock %} 里面的内容

这里列出一个 人物信息 的前段代码来做例子

 1 {% extends "rbac/base.html" %}
 2 
 3 {% block head %}
 4     <style>
 5      .clearfix:after,.clearfix:before{
 6             content: "";
 7             display:block;
 8         }
 9         .clearfix:after{
10             clear:both;
11         }
12         .clearfix{
13             zoom:1;
14         }
15 
16         .fl{
17             float: left;
18         }
19 
20         .fr{
21             float: right;
22         }
23         ul li{
24             float: left;
25             list-style: none;
26             text-indent: 0;
27         }
28         .id{
29             width:8%
30         }
31         .username, .password, .email, .nickname{
32             width: 20%;
33         }
34         .button{
35             width:12%;
36         }
37         .body ul{
38 {#            padding: 20px 0;#}
39             height:20px;
40             border-bottom: 1px solid #ddd;
41         }
42 {#        .add{#}
43 {#            position: relative;#}
44 {#            right:-100px;#}
45 {#            top:0;#}
46 {#        }#}
47 
48     </style>
49 {% endblock %}
50 
51 {% block body %}
52 <div class="clearfix body">
53     <a href="/rbac/user/add/" class="add"><input type="button" value="添加"></a>
54     <ul>
55         <li class="id">id</li>
56         <li class="username">username</li>
57         <li class="password">password</li>
58         <li class="email">email</li>
59         <li class="nickname">nickname</li>
60         <li class="button">编辑</li>
61     </ul>
62     {% for user_obj in user_obj_list %}
63         <ul>
64             <li class="id">{{ user_obj.id }}</li>
65             <li class="username">{{ user_obj.username }}</li>
66             <li class="password">{{ user_obj.password }}</li>
67             <li class="email">{{ user_obj.email }}</li>
68             <li class="nickname">{{ user_obj.nickname }}</li>
69             <li class="button">
70                 <a href="/rbac/user/edit/{{ user_obj.id }}/" class="edit"><input type="button" value="修改"></a>
71                 <a href="/rbac/user/delete/{{ user_obj.id }}/" class="delete"><input type="button" value="删除"></a>
72             </li>
73         </ul>
74     {% endfor %}
75 </div>
76 
77 {% endblock %}
用户信息编辑的前端代码

 

下面是我这个目录的结构

 

相关的配置文件

STATIC_URL = '/static/'

PERMISSION = {
    "rule": "^{0}$",
    "session_persission_list": "session_persission_list",
    "session_persission_menu_list": "session_persission_menu_list",
    "safe_url": ["/login.html", "/register.html", "/admin/.*", "/rbac/.*"],
}

  

        ModelForm

 

posted on 2017-09-26 17:57  何必从头  阅读(385)  评论(2)    收藏  举报

导航