rbac——界面权限

Posted xiugeng

tags:

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

一、模板继承

知识点:

  users.html / roles.html 继承自 base.html

  页面滚动时,固定

.menu {
    background-color: bisque;
    position: fixed;
    top: 60px;
    bottom: 0px;
    left: 0px;
    width: 200px;
}
.content {
    position: fixed;
    top: 60px;
    bottom: 0;
    right: 0;
    left: 200px;
    overflow: auto;  /* 滚动条 */
}

  base.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 引入 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <style>
        .header {
            width: 100%;
            height: 60px;
            background-color: #336699;
        }
        .menu {
            background-color: bisque;
            position: fixed;
            top: 60px;
            bottom: 0px;
            left: 0px;
            width: 200px;
        }
        .content {
            position: fixed;
            top: 60px;
            bottom: 0;
            right: 0;
            left: 200px;
            overflow: auto;  /* 滚动条 */
        }
    </style>
</head>
<body>

<div class="header">
    {{ user.name }}
</div>
<div class="contain">
    <div class="menu">
        menu
    </div>
    <div class="content">
        {% block con%}

        {% endblock %}
    </div>
</div>

</body>
</html>

  users.html:

{% extends ‘base.html‘ %}

{% block con %}
<h4>用户列表</h4>
    {% for user in user_list %}
    <p>{{ user }}</p>
    {% endfor %}
    
{% endblock con%}

  roles.html:

{% extends ‘base.html‘ %}

{% block con %}
<h4>角色列表</h4>
<ul>
    {% for role in role_list %}
        <p>{{ role }}</p>
    {% endfor %}
</ul>
{% endblock %}

二、在users.html中添加table

{% extends ‘base.html‘ %}

{% block con %}
    <h4>用户列表</h4>
    <a href="/users/add" class="btn btn-primary">添加用户</a>
    <table class="table table-bordered table-striped">
        <thead>
            <tr>
                <th>序号</th>
                <th>姓名</th>
                <th>角色</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            {% for user in user_list %}
                <tr>
                    <td>{{ forloop.counter }}</td>
                    <td>{{ user.name }}</td>
                    <td>
                        {% for role in user.roles.all %}
                            {{ role.title }}
                        {% endfor %}
                        
                    </td>
                    <td>
                        <a href="" class="btn btn-danger">删除</a>
                        <a href="" class="btn btn-warning">编辑</a>
                    </td>
                </tr>
            {% endfor %}
            
        </tbody>
    </table>
{% endblock %}

  注意:

(1)有一些用户有多重角色,需要将这些角色拿到显示在表格中的方法

<td>
    {% for role in user.roles.all %}
        {{ role.title }}
    {% endfor %}
</td>

(2)django模板中的forloop模板变量:在每个`` {% for %}``循环里有一个称为`` forloop`` 的模板变量。这个变量有一些提示循环进度信息的属性。

  forloop.counter 总是一个表示当前循环的执行次数的整数计数器。 这个计数器是从1开始的,所以在第一次循环时 forloop.counter 将会被设置为1。

三、根据权限决定是否显示按钮

  在页面往往有一些功能按钮,如果该用户没有权限,就不将这个按钮开放给当前用户,这样处理优于向用户提示“没有使用权限”。

1、在模板中权限按钮控制的简单形式

{# 根据是否有权限显示添加用户按钮 #}
{% if "/users/add" in  permission_list %}
    <a href="/users/add" class="btn btn-primary">添加用户</a>
{% endif %}

   处理带有正则表达式的url:

<td>
    {% if ‘/users/delete/(d+)‘ in permission_list %}
        <a href="/users/delete/{{ user.pk }}" class="btn btn-danger">删除</a>
    {% endif %}
    <a href="" class="btn btn-warning">编辑</a>
</td>

  这种方法是针对表做操作,根据表名去做判断。

  如果希望if判断时url里面不带有表名字。roles和users合并用一个视图函数来处理。

2、admin修改显示,页面显示更多内容

rbac/admin.py:

from django.contrib import admin
# Register your models here.
from .models import *

class PerConfig(admin.ModelAdmin):
    list_display = ["title", "url"]

admin.site.register(User)
admin.site.register(Role)
admin.site.register(Permission, PerConfig)

  注意:list_display = [] 。

  显示效果:

  技术分享图片

3、修改数据结构

   添加一个权限组表。将每张表的增删改查,划到一个组里面!无论多复杂的,最终一定是对数据库的(增删改查)。
  修改表结构,重新处理中间件,登录页面的目的:全是为了按钮的粒度,同一个模板,同一个视图,
显示不同的数据,权限。

(1)models.py代码

from django.db import models
# Create your models here.

class User(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    roles = models.ManyToManyField(to="Role")

    def __str__(self):
        return self.name

class Role(models.Model):
    title = models.CharField(max_length=32)
    permissions = models.ManyToManyField(to="Permission")

    def __str__(self):
        return self.title

class Permission(models.Model):
    title = models.CharField(max_length=32)
    url = models.CharField(max_length=32)
    # 操作
    action = models.CharField(max_length=32, default="")  # 默认值为空
    # 分组
    group = models.ForeignKey("PermissionGroup", default=1, on_delete=True)

    def __str__(self):
        return self.title

class PermissionGroup(models.Model):
    title = models.CharField(max_length=32)

    def __str__(self):
        return self.title

  修改完后,在一次执行数据库迁移。

(2)再一次修改rbac/admin.py:

from django.contrib import admin
# Register your models here.
from .models import *

class PerConfig(admin.ModelAdmin):
    list_display = ["title", "url", "group", "action"]

admin.site.register(User)
admin.site.register(Role)
admin.site.register(Permission, PerConfig)
admin.site.register(PermissionGroup)

(3)为权限添加action:

  技术分享图片

  全部修改后:

  技术分享图片

  修改之后,GROUP描述是对哪张表进行操作,ACTION是描述对这个表做什么操作

(4)修改rbac_permission表的group_id信息,将角色操作类别的group_id修改为2

  技术分享图片

4、重写inital_session(user, request)函数

# -*- coding:utf-8 -*-
__author__ = ‘Qiushi Huang‘


def inital_session(user,request):
    """
    查看当前用户所有的权限
    :param user:
    :param request:
    :return:
    """
    # 方案1:
    # permissions = user.roles.all().values("permissions__url").distinct()
    # print(permissions)  # <QuerySet [{‘permissions__url‘: ‘/users/‘}, {‘permissions__url‘: ‘/users/add‘}]>
    #
    # permission_list = []
    # for item in permissions:
    #     permission_list.append(item["permissions__url"])
    #
    # print(permission_list)
    #
    # request.session["permission_list"] = permission_list


    # 方案2:
    # 角色表跨到权限表查找
    permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action").distinct()
    print("permissions", permissions)  # 有一个权限QuerySet中就有一个字典
    """
    permissions <QuerySet [{‘permissions__url‘: ‘/users/‘, 
                            ‘permissions__group_id‘: 1, 
                            ‘permissions__action‘: ‘list‘}]>
    """
    # 对上述数据进行处理: 以组为键,以字典为值
    permission_dict = {}
    for item in permissions:
        gid = item.get("permissions__group_id")

        if not gid in permission_dict:
            permission_dict[gid] = {
                "urls": [item["permissions__url"], ],
                "actions": [item["permissions__action"], ]

            }
        else:
            # 组id已经在字典中
            permission_dict[gid]["urls"].append(item["permissions__url"])
            permission_dict[gid]["actions"].append(item["permissions__action"])

    print(permission_dict)  # {1: {‘urls‘: [‘/users/‘, ‘/users/add‘, ‘/users/delete/(\\d+)‘, ‘/users/edit/(\\d+)‘],
    #                              ‘actions‘: [‘list‘, ‘add‘, ‘delete‘, ‘edit‘]}}

    request.session[‘permission_dict‘]=permission_dict

注意:

 (1)在session中注册权限字典

  前面是在session中注册权限列表:

request.session[‘permission_list‘] = permission_list

   现在需要在session中注册的是权限字典:

request.session[‘permission_dict‘] = permission_dict

(2)从角色表到权限表跨表查询权限路径、权限组ID、权限action

# 角色表跨到权限表查找
permissions = user.roles.all().values("permissions__url", "permissions__group_id", "permissions__action").distinct()
print("permissions", permissions)  # 有一个权限QuerySet中就有一个字典
"""
permissions <QuerySet [{‘permissions__url‘: ‘/users/‘, 
                        ‘permissions__group_id‘: 1, 
                        ‘permissions__action‘: ‘list‘}]>
"""

(3)对上述数据进行处理:以组为键、以字典为值

{
    1: {
        "url": [‘/users/‘,],
        "actions": [‘list‘,]
    },
}
如果用户操作多个权限:
{
    1: {
        ‘urls‘: [‘/users/‘, ‘/users/add/‘, ‘/users/delete/(\\d+)/‘, ‘/users/edit/(\\d+)/‘], 
        ‘actions‘: [‘list‘, ‘add‘, ‘delete‘, ‘edit‘]
        }, 
}
如果除了有用户操作权限还有角色操作权限:
{
    1: {
        ‘urls‘: [‘/users/‘, ‘/users/add/‘, ‘/users/delete/(\\d+)/‘, ‘/users/edit/(\\d+)/‘], 
        ‘actions‘: [‘list‘, ‘add‘, ‘delete‘, ‘edit‘]
        }, 
    2: {
        ‘urls‘: [‘/roles/‘], 
        ‘actions‘: [‘list‘]
        }
}

5、改写中间件rbac.py中的VaildPermission类

# -*- coding:utf-8 -*-
__author__ = ‘Qiushi Huang‘

import re
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse, redirect


class ValidPermission(MiddlewareMixin):

    def process_request(self, request):

        # 当前访问路径
        current_path = request.path_info  # 当前路径的属性

        ########### 检查是否属于白名单 #############
        valid_url_list = [‘/login/‘, ‘/reg/‘, ‘/admin/.*‘]
        for valid_url in valid_url_list:
            ret = re.match(valid_url, current_path)
            if ret:
                return   # 等同于return none

        ############### 检验是否登录 ##############
        user_id = request.session.get("user_id")

        if not user_id:
            return redirect("/login/")

        ################ 校验权限1 #################
        # permission_list = request.session.get("permission_list")
        #
        # flag = False
        # for permission in permission_list:
        #     permission = "^%s$" % permission
        #     ret = re.match(permission, current_path)  # 第一个参数是匹配规则,第二个参数是匹配项
        #     if ret:
        #         flag = True
        #         break
        # if not flag:
        #     # 如果没有访问权限
        #     return HttpResponse("没有访问权限!")

        ################ 校验权限2 #################
        permission_dict = request.session.get(‘permission_dict‘)

        for item in permission_dict.values():  # 循环只取字典的值
            urls = item["urls"]
            for reg in urls:
                reg = "^%s$" % reg
                ret = re.match(reg, current_path)
                if ret:
                    print("actions", item["actions"])
                    request.actions = item["actions"]
                    return None

        return HttpResponse("没有访问权限!")

 注意:

(1)中间件的request对象,给对象添加属性actions,未来视图中就可以通过request.actions拿到当前用户对这个表的所有操作权限。

request.actions = item["actions"]

(2)数据类型从数组变为了字典,数据处理方式略有不同。

6、改写users视图,视图添加Per类

class Per(object):
    def __init__(self, actions):
        self.actions = actions

    def add(self):
        return "add" in self.actions

    def delete(self):
        return "delete" in self.actions

    def edit(self):
        return "edit" in self.actions

    def list(self):
        return "list" in self.actions


def users(request):
    user_list = User.objects.all()
    permission_list = request.session.get("permission_list")
    print(permission_list)  # [‘/users/‘, ‘/users/add‘, ‘/roles/‘, ‘/users/delete/(\\d+)‘, ‘/users/edit/(\\d+)‘]

    # 查询当前登录人的名字
    id = request.session.get("user_id")
    user = User.objects.filter(id=id).first()

    per = Per(request.actions)

    return render(request, "users.html", locals())

 注意:

  通过Per(request.actions)得到per对象,传到模板中可以通过per.editper.list等方式来判断是否拥有权限。增加阅读性。

 7、users.html改写

{% extends ‘base.html‘ %}

{% block con %}
    <h4>用户列表</h4>
    {# 根据是否有权限显示添加用户按钮 #}
    {% if per.add %}
        <a href="/users/add" class="btn btn-primary">添加用户</a>
    {% endif %}
    <table class="table table-bordered table-striped">
        <thead>
            <tr>
                <th>序号</th>
                <th>姓名</th>
                <th>角色</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            {% for user in user_list %}
                <tr>
                    <td>{{ forloop.counter }}</td>
                    <td>{{ user.name }}</td>
                    <td>
                        {% for role in user.roles.all %}
                            {{ role.title }}
                        {% endfor %}
                    </td>
                    <td>
                        {% if per.delete %}
                            <a href="/users/delete/{{ user.pk }}" class="btn btn-danger">删除</a>
                        {% endif %}
                        {% if per.edit %}
                            <a href="" class="btn btn-warning">编辑</a>
                        {% endif %}
                    </td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}

   显示效果:

  技术分享图片

四、总结

1、权限粒度控制

简单控制:
{% if "users/add" in permissions_list%}

2、更改数据库结构

class Permission(models.Model):
    title = models.CharField(max_length=32)
    url = models.CharField(max_length=32)
    # 操作
    action = models.CharField(max_length=32, default="")  # 默认值为空
    # 分组
    group = models.ForeignKey("PermissionGroup", default=1, on_delete=True)
def __str__(self): return self.title class PermissionGroup(models.Model): title = models.CharField(max_length=32)
def __str__(self): return self.title

3、登录验证

permissions = user.roles.all().values("permissions__url","permissions__group_id","permissions__action").distinct()

4、构建permission_dict

5、中间件校验权限

permission_dict = request.session.get(‘permission_dict‘)

for item in permission_dict.values():  # 循环只取字典的值
    urls = item["urls"]
    for reg in urls:
        reg = "^%s$" % reg
        ret = re.match(reg, current_path)
        if ret:
            print("actions", item["actions"])
            request.actions = item["actions"]
            return None

return HttpResponse("没有访问权限!")

 

以上是关于rbac——界面权限的主要内容,如果未能解决你的问题,请参考以下文章

前端如何配合后端完成RBAC权限控制

[PHP] RBAC权限与审批流的简单数据库构想

RBAC从入门到精通

Yii-backadmin|Yii2做的rbac后台通用权限的管理系统

权限想要细化到按钮,怎么做?

权限想要细化到按钮,怎么做?