权限管理系统

Posted wq-mr-almost

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了权限管理系统相关的知识,希望对你有一定的参考价值。

一、需求分析:

用户登录
- 获取当前用户具有的所有角色
- 获取当前用户具有的所有权限(去重)

  -在访问列表页面时,需要判断:有无添加权限,有无删除权限,有无编辑权限;在页面上显示相应权限按钮

  -访问菜单URL时,默认展开其所属菜单

  -访问非菜单URL,默认选中原菜单

 

 

 二、方法步骤

a.首先建一个名为rbac的APP

b.创建表结构,基于角色权限控制、菜单:

  -5个类,7张表


 技术分享图片

 

 

技术分享图片
from django.db import models


class Menu(models.Model):
    """
    菜单组
    """
    title = models.CharField(max_length=32)
    
    class Meta:
        verbose_name_plural = "菜单组"

    def __str__(self):
        return self.title


class Group(models.Model):
    """
    权限组
    """
    caption = models.CharField(verbose_name=组名称, max_length=16)
    menu = models.ForeignKey(verbose_name=所属菜单, to=Menu, default=1)
    
    class Meta:
        verbose_name_plural = "权限组"

    def __str__(self):
        return self.caption


class Permission(models.Model):
    """
    权限表
    """
    title = models.CharField(verbose_name=标题, max_length=32)
    url = models.CharField(verbose_name="含正则URL", max_length=64)

    menu_gp = models.ForeignKey(verbose_name=组内菜单, to=Permission, null=True, blank=True, related_name=x1)

    code = models.CharField(verbose_name="操作", max_length=16)
    group = models.ForeignKey(verbose_name=所属组, to="Group")

    class Meta:
        verbose_name_plural = "权限表"

    def __str__(self):
        return self.title


class User(models.Model):
    """
    用户表
    """
    username = models.CharField(verbose_name=用户名, max_length=32)
    password = models.CharField(verbose_name=密码, max_length=64)
    email = models.CharField(verbose_name=邮箱, max_length=32)

    roles = models.ManyToManyField(verbose_name=具有的所有角色, to="Role", blank=True)

    class Meta:
        verbose_name_plural = "用户表"

    def __str__(self):
        return self.username


class Role(models.Model):
    """
    角色表
    """
    title = models.CharField(max_length=32)
    permissions = models.ManyToManyField(verbose_name=具有的所有权限, to=Permission, blank=True)

    class Meta:
        verbose_name_plural = "角色表"

    def __str__(self):
        return self.title
models.py

 

 

verbose_name---在admin模式下,字段显示的名字
blank=True  ---在admin模式下,字段可以为空
class Meta:
verbose_name_plural = "表名"  ---在admin模式下,显示的表名

为了让我们的表能在admin模式下显示,我们需要在rbac文件下的admin.py中,进行如下设置

 

技术分享图片
from django.contrib import admin

from . import models


admin.site.register(models.Permission)
admin.site.register(models.User)
admin.site.register(models.Role)
admin.site.register(models.Group)
admin.site.register(models.Menu)
/rbac/admin.py

 

 

 

c.基于Django admin录入权限数据 

  python manage.py createsuperuser

 

d.用户登录程序

  按照逻辑,当我们输入一个URL,在跳转到相应页面前,我们需要做3件事:

  1.首先应该先判断此URL,是否限制用户浏览,如果没有限制,则不需要登录,直接进入相应页面(可在settings中设置白名单实现)

        2.若限制了,则需要检验用户的访问权限,在检验用户权限之前,我们得先检验用户登录状态(通过判断登陆成功后设置的session值是否存在),拿到用户权限信息(先比对账户密码,正确则设置一个session,用作检验登陆状态)

        3.如果是登录的状态,我们还需要检验用户是否有浏览此页面的权限(为此我们需要把当前URL和用户所有权限里的URL进行比对),如能访问此URL,当进入URL后,我们还需要根据用户的身份,在页面上显示不同的操作权限(如增删改查,有哪些操作权限就让哪些操作权限显示,没有的就不让其显示出来)

 

由于每次跳转一个URL前,都会重复上面的步骤,我们可以将上面的方法代码写入中间件中,以免为此给每个视图函数都加上一个装饰器(麻烦)

 

比对账户密码,设置session的过程应该是在视图函数里完成,由于我们在中间件中会检验登陆状态时会用到session,所以我们可以用户的一些权限信息放到session中

现在我们需要考虑的是session中到底需要存放哪些信息?

首先,我们来考虑一下,作为权限管理系统,我们希望能根据用户的权限,在页面显示一些相应菜单信息和操作按钮,所以session设置,我们分两个部分:

一个是权限相关,用来检验用户访问权限,以及页面权限相关按钮(添加、编辑、删除)显示与否

二是菜单相关,比如我们点击菜单一里的子选项:用户列表,显示页面信息后,菜单一仍然是展开状态,当我们在用户列表点击添加用户,跳转新页面时,仍然展开菜单一

 

session权限相关

根据需求,我们知道,权限相关里,我们需要拿到用户可访问的所有页面(url,用来匹配访问权限)用户所有可执行的操作(code,用来匹配可显示的操作键)、和(permissions__group_id)用来把权限归类

为了方便后取数据,所以我们最终想要的结构是

{group_id1:{

  urls:[url1、url2、url3....],

  codes:[list、del、edit、add]},

group_id2:{

  urls:[url1、url2、url3....],

  codes:[list、del、edit、add]}

 

session菜单相关

根据需求,我们知道,权限相关里,我们需要menu_title(大菜单标题)menu_id(大菜单的id,用来通过此id归类子菜单)menu_gp_id(组内id,用于判断是否可以作为子菜单,以及非子菜单所属的子菜单)permission_id(通过可做菜单的id,分组其下面的url)permission_title(子菜单的标题)url(用于放在a标签里作跳转链接,如果不是子菜单,则打开的是此链接,其所属组所在菜单栏应该展开)

 

所以我们一共要取出

技术分享图片
    list = user.roles.values(permissions__code,  # 权限操作
                             permissions__title,  # 权限名
                             permissions__url,  # 权限url
                             permissions__id,  # 权限id
                             permissions__menu_gp_id,  # 组内菜单id
                             permissions__group_id,  # 权限组id
                             permissions__group__menu__id,  # 菜单id
                             permissions__group__menu__caption,  # 菜单名
                             ).distinct()
技术分享图片

一定要去重,因为有的人有几个身份,有的权限会重复,将上面拿到的数据,再处理一下,分别作为,菜单相关,权限相关所需的数据

由于以上步骤过多,我们应该从视图函数里抽离出来,单独在rbac文件夹下建一个service文件夹,里面建一个名为init_permission.py的初始化文件

技术分享图片
from django.conf import settings


def init_permission(user,request):
    """
    初始化权限信息,获取权限信息并放置到session中。
    """
    permission_list = user.roles.values(permissions__id,
                                        permissions__title,              # 用户列表
                                        permissions__url,
                                        permissions__code,
                                        permissions__menu_gp_id,         # 组内菜单ID,Null表示是菜单
                                        permissions__group_id,
                                        permissions__group__menu_id,     # 菜单ID
                                        permissions__group__menu__title,#  菜单名称
                                        ).distinct()     #要去重,因为每个人可能有多种身份,权限会重复

    # 菜单相关(以后再匹配)
    sub_permission_list = []
    for item in permission_list:
        tpl = {
            id:item[permissions__id],
            title:item[permissions__title],
            url:item[permissions__url],
            menu_gp_id:item[permissions__menu_gp_id],
            menu_id:item[permissions__group__menu_id],
            menu_title:item[permissions__group__menu__title],
        }
        sub_permission_list.append(tpl)
    request.session[settings.PERMISSION_MENU_KEY] = sub_permission_list


    # 权限相关
    result = {}
    for item in  permission_list:
        group_id = item[permissions__group_id]
        code = item[permissions__code]
        url = item[permissions__url]
        if group_id in result:
            result[group_id][codes].append(code)
            result[group_id][urls].append(url)
        else:
            result[group_id] = {
                codes:[code,],
                urls:[url,]
            }

    request.session[settings.PERMISSION_URL_DICT_KEY] = result
init_permission.py

我们只需要在login的视图函数里验证完用户名密码后,调用函数就行

init_permission(user,request)

要记得先导入,才能成功调用

from rbac.service.init_permission import init_permission

这样我们就完成了session值的设置,接下来我们可以写我们的中间件了

 

在rbac中建立一个middlewares文件夹,然后建一个名为rbac.py的文件,用来编写我们自定义的中间件代码

技术分享图片
import re

from django.shortcuts import redirect,HttpResponse
from django.conf import settings

class MiddlewareMixin(object):
    def __init__(self, get_response=None):
        self.get_response = get_response
        super(MiddlewareMixin, self).__init__()

    def __call__(self, request):
        response = None
        if hasattr(self, process_request):
            response = self.process_request(request)
        if not response:
            response = self.get_response(request)
        if hasattr(self, process_response):
            response = self.process_response(request, response)
        return response


class RbacMiddleware(MiddlewareMixin):

    def process_request(self,request):
        # 1. 获取当前请求的URL
        # request.path_info
        # 2. 获取Session中保存当前用户的权限
        # request.session.get("permission_url_list‘)
        current_url = request.path_info

        # 当前请求不需要执行权限验证
        for url in settings.VALID_URL:
            if re.match(url,current_url):
                return None

        #在视图函数那边验证用户登录成功后,会设置session,如果登录成功则会有此session
        permission_dict = request.session.get(settings.PERMISSION_URL_DICT_KEY)
        if not permission_dict:
            #没有session值,则说明没有登录,需要跳转登录页面
            return redirect(/login/)

        flag = False
        for group_id,code_url in permission_dict.items():

            for db_url in code_url[urls]:
                regax = "^{0}$".format(db_url)  #为了精准匹配
                # 将当前页面与当前用户可访问的页面进行匹配
                if re.match(regax, current_url):
                    #匹配成功后将用户的可执行的(增、删、改、列表等操作)存到request中,方便调用
                    request.permission_code_list = code_url[codes]
                    flag = True
                    break

            if flag:
                break
        #匹配失败,则显示无权访问
        if not flag:
            return HttpResponse(无权访问)
rbac/middlewares/rbac.py

 

写完自定义中间件后,记得去settings.py中加上我们写的中间件,来让我们的中间件生效

MIDDLEWARE = [‘rbac.middlewares.rbac.RbacMiddleware‘,] 

在settings中,我们还需要写上我们自定义的字符串

PERMISSION_URL_DICT_KEY = "permission_url_dict"

之所以这样写,是为了方便以后修改时,只需要修改settings里的值,其他引用到的地方都会自动应用

还有加上URL白名单

VALID_URL = [
    "/login/",
    "/admin.*"
]

 

由于页面上都会显示菜单,所以我们先写一个模板,其他的只有继承就好

技术分享图片
{% load rbac %}    {# 引用自定义标签 #}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/rbac/rbac.css" />
    <title>Title</title>
</head>
<body>
        <div style="float: left;width: 20%;height: 500px;background-color: #dddddd">
            {% menu_html request %}  {# 调用自定义menu_html标签,并传入request参数 #}
        </div>
        <div style="float: left;width: 80%">
            {% block content %}{% endblock %}
        </div>

            <script src="/static/jquery-3.2.1.min.js"></script>
            <script src="/static/rbac/rbac.js"></script>

</body>
</html>
layout

 

在rbac下建立一个static再在里面建立rbac文件夹,在建我们的css和js文件

技术分享图片
.item-permission{
    padding: 3px 10px;
}
.item-permission a{
    display: block;
}
.item-permission a.active{
    color: red;
}
.hide{
    display: none;
}
rbac.css
技术分享图片
/**
 * Created by Administrator on 2017/11/8.
 */
 $(function () {
    $(.item-title).click(function () {
        if($(this).next().hasClass(hide)){
            $(this).next().removeClass(hide)
        }else{
            $(this).next().addClass(hide)
        }
    })


});
rbac.js

 

注意根目录下一定要有static文件下,否则我们的css,JS可能不生效

jquery文件我们可以放在根目录下的static文件夹中

 

接下来我们写自定义标签的内容在rbac下建立templatetags文件下,创建rbac.py

技术分享图片
import re
from django.template import Library
from django.conf import settings
register = Library()

@register.inclusion_tag("xxxxx.html")
def menu_html(request):
    """
    去Session中获取菜单相关信息,匹配当前URL,生成菜单
    :param request:
    :return:
    """
    menu_list = request.session[settings.PERMISSION_MENU_KEY]
    current_url = request.path_info

    menu_dict = {}
    for item in menu_list:
        if not item[menu_gp_id]:
            menu_dict[item[id]] = item

    for item in menu_list:
        regex = "^{0}$".format(item[url])
        if re.match(regex,current_url):
            menu_gp_id = item[menu_gp_id]
            if menu_gp_id:
                menu_dict[menu_gp_id][active] = True
            else:
                menu_dict[item[id]][active] = True

    result = {}
    for item in menu_dict.values():
        active = item.get(active)
        menu_id = item[menu_id]
        if menu_id in result:
            result[menu_id][children].append({ title: item[title], url: item[url],active:active})
            if active:
                result[menu_id][active] = True
        else:
            result[menu_id] = {
                menu_id:item[menu_id],
                menu_title:item[menu_title],
                active:active,
                children:[
                    { title: item[title], url: item[url],active:active}
                ]
            }

    return {menu_dict:result}
rbac.py

 

此自定义标签中函数的作用是去Session中获取菜单相关信息,匹配当前URL,生成菜单

 1.首先我们需要从用户的权限中拿出菜单相关的信息,并获取当前访问的URL

 menu_list = request.session[settings.PERMISSION_MENU_KEY] 

 current_url = request.path_info 

2.从menu_list中获取用户权限里能作为菜单的权限:

    menu_dict = {}
    for item in menu_list:
        if not item[menu_gp_id]:
            menu_dict[item[id]] = item

3.匹配当前URL,判断当前URL是否可做菜单,如果可以,则展开此菜单,如果不是菜单,则将其所属菜单展开(给菜单加状态)

技术分享图片
for item in menu_list:
    regex = "^{0}$".format(item[url])
    if re.match(regex,current_url):
        menu_gp_id = item[menu_gp_id]
        if menu_gp_id:    #不是子菜单则给其所属子菜单加上展开状态
            menu_dict[menu_gp_id][active] = True
        else:   #是子菜单则给自己加上展开状态
            menu_dict[item[id]][active] = True
技术分享图片

4,菜单相关信息已经整理完毕,存在了menu_dict中,接下来就要创建菜单结构,将菜单数据结构化,将同属一个大菜单下的菜单键放一个大菜单下

技术分享图片
result = {}
for item in menu_dict.values():
    active = item.get(active)
    menu_id = item[menu_id]
    if menu_id in result: #一个大菜单下有多个子菜单,则只需要将子菜单的item和url以及状态加入即可
        result[menu_id][children].append({ title: item[title], url: item[url],active:active})
        if active:  #只要子菜单是激活状态,则大菜单应该也是激活状态
            result[menu_id][active] = True
    else:
        result[menu_id] = {
            menu_id:item[menu_id],
            menu_title:item[menu_title],
            active:active,
            children:[
                { title: item[title], url: item[url],active:active}
            ]
        }
技术分享图片

 

 

@register.inclusion_tag("xxxxx.html")作用是:可以将其所装饰的函数menu_html的返回值直接给xxxxx.html(此页面是用来生成菜单相关的html)中使用,

当调用自定义标签menu_html时,会将经过渲染后的xxxxx.html页面以字符串的形式返回给调用的地方

用来形成菜单

 

 

这样以后其他页面直接继承layout.html就会有其对应的菜单,内容部分,我们还需要判断一下用户的操作权限,以显示相应操作按钮,如userinfo页面

技术分享图片
from django.conf.urls import url
from django.contrib import admin
from rbac import views
from app01 import views as app01_views
urlpatterns = [
    url(r^admin/, admin.site.urls),
    url(r^test/, views.test),
    url(r^login/, app01_views.login),
    url(r^index/, app01_views.index),
    url(r^userinfo/$, app01_views.userinfo),
    url(r^userinfo/add/$, app01_views.userinfo_add),
    url(r^order/$, app01_views.order),
    url(r^order/add/$, app01_views.order_add),
]
permissionurls.py
技术分享图片
class BasePagePermission(object):
    #定义这个类,是为了html页面方便调用判断
    def __init__(self,code_list):
        self.code_list = code_list

    def has_add(self):
        if "add" in self.code_list:
            return True

    def has_edit(self):
        if edit in self.code_list:
            return True
    def has_del(self):
        if del in self.code_list:
            return True

def userinfo(request):
    page_permission = BasePagePermission(request.permission_code_list)
    data_list = [
        {id:1,name:xxx1},
        {id:2,name:xxx2},
        {id:3,name:xxx3},
        {id:4,name:xxx4},
        {id:5,name:xxx5},
    ]
    return render(request,userinfo.html,{data_list:data_list,page_permission:page_permission})


def userinfo_add(request):
    page_permission = BasePagePermission(request.permission_code_list)
    return render(request, userinfo_add.html, { page_permission: page_permission})


class OrderPagePermission(BasePagePermission):
    #继承基础的,方便扩展
    def has_report(self):
        if report in self.code_list:
            return True

def order(request):
    order_permission = OrderPagePermission(request.permission_code_list)


    return render(request,order.html)
permissionapp01views.py
技术分享图片
{% extends "layout.html" %}


{% block content %}
    {% if page_permission.has_add %}
                <a href="/userinfo/add/">添加</a>
            {% endif %}
            <table>
                    {% for row in data_list %}
                        <tr>
                            <td>{{ row.id }}</td>
                            <td>{{ row.name }}</td>
                             {% if  page_permission.has_edit %}
                            <td><a href="#">编辑</a></td>
                            {% endif %}
                            {% if  page_permission.has_del %}
                            <td><a href="#">删除</a></td>
                            {% endif %}
                        </tr>
                {% endfor %}

            </table>
{% endblock %}
templatesuserinfo.html

 

 

三、总结

rbac里有什么?

技术分享图片

 

settings中有什么?

技术分享图片
#########注册rbac的app,使其生效########
INSTALLED_APPS = [
    rbac.apps.RbacConfig,   ###也可直接写rbac
]

#########加入我们自定义的中间件,使其生效########
MIDDLEWARE = [
    rbac.middlewares.rbac.RbacMiddleware,
]


#############CSS,JS,JQ############
STATICFILES_DIRS = (
    os.path.join(BASE_DIR,static),
)

##########Session中用到的变量#############
PERMISSION_URL_DICT_KEY = "permission_url_dict"
PERMISSION_MENU_KEY = "permission_menu_key"

#####白名单############

VALID_URL = [
    "/login/",
    "/admin.*"
]
技术分享图片

 

使用方法

1.创建project

2.复制rbac文件

3.在settings中按上面的设置

4.去admin中配置文件,录入权限信息

5.在登陆逻辑中,登陆成功时,调用init_permission方法(为此要在视图函数中导入

from rbac import models
from rbac.service.init_permission import init_permission

 

6.视图函数中用来判断是否具有code操作

技术分享图片
class BasePagePermission(object):
    #定义这个类,是为了html页面方便调用判断
    def __init__(self,code_list):
        self.code_list = code_list

    def has_add(self):
        if "add" in self.code_list:
            return True

    def has_edit(self):
        if edit in self.code_list:
            return True
    def has_del(self):
        if del in self.code_list:
            return True

def userinfo(request):
    page_permission = BasePagePermission(request.permission_code_list)
    data_list = [
        {id:1,name:xxx1},
        {id:2,name:xxx2},
        {id:3,name:xxx3},
        {id:4,name:xxx4},
        {id:5,name:xxx5},
    ]
    return render(request,userinfo.html,{data_list:data_list,page_permission:page_permission})


def userinfo_add(request):
    page_permission = BasePagePermission(request.permission_code_list)
    return render(request, userinfo_add.html, { page_permission: page_permission})


class OrderPagePermission(BasePagePermission):
    #继承基础的,方便扩展
    def has_report(self):
        if report in self.code_list:
            return True

def order(request):
    order_permission = OrderPagePermission(request.permission_code_list)

    return render(request,order.html)
技术分享图片

前端页面只需要

{% if page_permission.has_add %}
    <a href="/userinfo/add/">添加</a>
{% endif %}



7. 模板中:
{%load rbac %}  加载自定义标签

<link rel="stylesheet" href="/static/rbac/rbac.css" />  引用rbac中的CSS



{% menu_html  request %}      调用自定义menu_html标签,并传入request参数

 

<script src="/static/jquery-3.2.1.min.js"></script>
<script src="/static/rbac/rbac.js"></script>  引用rbac中的JS









以上是关于权限管理系统的主要内容,如果未能解决你的问题,请参考以下文章

Oracle-常用数据库对象笔记(片段)

教程4 - 验证和权限

JS+JavaBean判断管理员增删改的操作权限

Django REST框架--认证和权限

片段中的请求权限

片段中的请求权限不显示对话框