如何实现后台管理系统中的权限管理?

Posted

tags:

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

参考技术A 登录成功后,服务端会返回一个 token(该token的是一个能唯一标示用户身份的一个key),之后我们将token存储在本地localstorage之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不用再去登录页面重新登录了。

为了保证安全性,后台所有token有效期(Expires/Max-Age)都是Session,就是当浏览器关闭了就丢失了。重新打开浏览器都需要重新登录验证,后端也会在每周固定一个时间点重新刷新token,让后台用户全部重新登录一次,确保后台用户不会因为电脑遗失或者其它原因被人随意使用账号。

用户登录成功之后,我们会在全局钩子router.beforeEach中拦截路由,判断是否已获得token,在获得token之后我们就要去获取用户的基本信息了

页面会先从 localstorage中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有token,就会把这个 token 返给后端去拉取user_info,保证用户信息是最新的。 当然如果是做了单点登录得的的话,用户信息存储在本地也是可以的。当你一台电脑登录时,另一台会被提下线,所以总会重新登录获取最新的内容。

前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoutes动态挂载路由。但这些控制都只是页面级的,说白了前端再怎么做权限控制都不是绝对安全的,后端的权限验证是逃不掉的。

前端控制页面级的权限,不同权限的用户显示不同的侧边栏和限制其所能进入的页面(也做了少许按钮级别的权限控制),后端则会验证每一个涉及请求的操作,验证其是否有该操作的权限,每一个后台的请求不管是 get 还是 post 都会让前端在请求 header里面携带用户的 token,后端会根据该 token 来验证用户是否有权限执行该操作。若没有权限则抛出一个对应的状态码,前端检测到该状态码,做出相对应的操作。

具体实现:
1.创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。
2.当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
3.调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
4.使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件。

Django 权限管理-后台根据用户权限动态生成菜单

Django权限管理

实现目标:

1、管理用户,添加角色,用户关联角色

2、添加权限、角色关联权限

3、添加动作、权限关联动作

4、添加菜单、权限关联菜单

实现动态生成用户权限菜单(可设置多级菜单嵌套)、根据前台URL自动选中菜单并折叠其余菜单

 

最终实现类似这样的效果:

菜单一
  菜单1.1
  菜单1.2
    菜单1.2.1
      订单管理
      分类管理

菜单二

 

 

一、首先是建立表格

models

from django.db import models


# Create your models here.
class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

    class Meta:
        verbose_name_plural = 用户表

    def __str__(self):
        return self.username


class Role(models.Model):
    role = models.CharField(max_length=32)

    class Meta:
        verbose_name_plural = 角色表

    def __str__(self):
        return self.role


class User2Role(models.Model):
    u = models.ForeignKey(User, on_delete=models.CASCADE)
    r = models.ForeignKey(Role, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = 用户分配角色

    def __str__(self):
        return %s-%s % (self.u.username, self.r.role)


class Menu(models.Model):
    caption = models.CharField(max_length=32)
    parent = models.ForeignKey(self, related_name=p, null=True, blank=True, on_delete=models.CASCADE)

    def __str__(self):
        return %s % (self.caption,)


class Permission(models.Model):
    caption = models.CharField(max_length=32)
    url = models.CharField(max_length=32)
    menu = models.ForeignKey(Menu, null=True, blank=True, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = URL表

    def __str__(self):
        return %s-%s % (self.caption, self.url)


class Action(models.Model):
    caption = models.CharField(max_length=32)
    code = models.CharField(max_length=32)

    class Meta:
        verbose_name_plural = 操作表

    def __str__(self):
        return self.caption


class Permission2Action(models.Model):
    p = models.ForeignKey(Permission, on_delete=models.CASCADE)
    a = models.ForeignKey(Action, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = 权限表

    def __str__(self):
        return %s-%s:-%s?t=%s % (self.p.caption, self.a.caption, self.p.url, self.a.code)


class Permission2Action2Role(models.Model):
    p2a = models.ForeignKey(Permission2Action, on_delete=models.CASCADE)
    r = models.ForeignKey(Role, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = 角色分配权限

    def __str__(self):
        return %s=>%s % (self.r.role, self.p2a)

 

建立表后,用django的admin在表中添加一些数据

1、用户表:建立几个用户

2、角色表:建立几个角色,如:CEOCTO开发客服业务员

3、给用户分配角色

4、URL表:建立几个管理菜单,如:分类管理报表管理订单管理用户管理

5、操作表:增删改查

6、权限表:给URL添加操作内容

7、角色分配权限:

8、菜单表:设置三层菜单,如:菜单一:菜单1.1:菜单1.1.1

 

 

二、url

添加数据后建立url

urlpatterns = [
    path(admin/, admin.site.urls),
    path(login/, views.login),
    path(logout/, views.logout),
    path(index/, views.index),
]

 

三、login

编辑login,登录成功后,将用户信息保存在session中,并通过MenuHelper类获取用户的权限和菜单,也保存在session中,实例化MenuHelper类

def login(request):
    if request.method == "GET":
        return render(request, login.html)
    else:
        username = request.POST.get(username)
        pwd = request.POST.get(pwd)
        obj = models.User.objects.filter(username=username, password=pwd).first()
        if obj:
            # 登录成功,获取当前用户信息
            # 放到session中
            request.session[user_info] = {nid: obj.id, username: obj.username}

            # 获取当前用户的所有权限,获取所有菜单,获取在菜单中显示的权限(叶子节点)
            # 放到session中
            MenuHelper(request, obj.username)
            return redirect(/index)
        else:
            return redirect(/login)

 

四、MenuHelper类

首先获取当前用户,通过reques.path_info获取当前用户访问的url

调用类的session_data方法:判断该用户当前session中是否已经有内容,如果有内容则与取出session中的内容,否则通过用户名分别获取当前用户的角色列表、权限列表、最终显示的菜单列表以及所有菜单,随后跳转至index

class MenuHelper(object):
    def __init__(self, request, username):
        # 当前请求的request对象
        self.request = request
        # 当前用户名
        self.username = username
        # 当前url,如用户访问127.0.0.1:8000/index.html?p=123 会获得:index.html
        self.current_url = request.path_info

        # 当前用户的所有权限
        self.permission2action_dict = None
        # 当前用户菜单中显示的所有权限(叶子节点)
        self.menu_leaf_list = None
        # 所有菜单
        self.menu_list = None

        self.session_data()

    def session_data(self):
        permission_dict = self.request.session.get(permission_info)
        if permission_dict:
            self.permission2action_dict = permission_dict[permission2action_dict]
            self.menu_leaf_list = permission_dict[menu_leaf_list]
            self.menu_list = permission_dict[menu_list]
        else:
            # 获取当前用户的角色列表
            role_list = models.Role.objects.filter(user2role__u__username=self.username)

            # 获取当前用户的权限列表(url+action)
            permission2action_list = models.Permission2Action.objects.                filter(permission2action2role__r__in=role_list).                values(p__url, a__code).distinct()

            permission2action_dict = {}
            for item in permission2action_list:
                if item[p__url] in permission2action_dict:
                    permission2action_dict[item[p__url]].append(item[a__code])
                else:
                    permission2action_dict[item[p__url]] = [item[a__code], ]

            # 获取菜单的叶子节点,即:菜单的最后一层应该显示的权限
            menu_leaf_list = list(models.Permission2Action.objects.
                                  filter(permission2action2role__r__in=role_list).
                                  exclude(p__menu__isnull=True).
                                  values(p_id, p__url, p__caption, p__menu).distinct())

            # 获取所有菜单列表
            menu_list = list(models.Menu.objects.values(id, caption, parent_id))

            self.request.session[permission_info] = {
                permission2action_dict: permission2action_dict,
                menu_leaf_list: menu_leaf_list,
                menu_list: menu_list,
            }

            # self.permission2action_list = permission2action_list
            # self.menu_leaf_list = menu_leaf_list
            # self.menu_list = menu_list

    def menu_data_list(self):
        # 设置一个空的叶子节点字典
        menu_leaf_dict = {}
        # 首先设置叶子父id节点为空
        open_left_parent_id = None

        for item in self.menu_leaf_list:
            # 将获取的叶子节点列表的每一个值转换为字典形式,并重新设置key,添加child,status,open字段
            item = {
                id: item[p_id],
                url: item[p__url],
                caption: item[p__caption],
                parent_id: item[p__menu],
                child: [],
                status: True,  # 是否显示
                open: False,  # 是否打开
            }
            # 判断每一个叶子节点的父节点,将每个叶子节点的内容添加至父节点id作为的key中
            # 判断父节点id作为的key是否在叶子节点字典中存在,如果存在,则将item值append进入
            if item[parent_id] in menu_leaf_dict:
                menu_leaf_dict[item[parent_id]].append(item)
            # 如果不存在,则直接在列表中生成一个key是叶子节点父节点id的,值为item的数据
            else:
                menu_leaf_dict[item[parent_id]] = [item, ]

            # 判断用户输入的url是否与现在的url匹配,item[‘url‘]可以写成一个正则表达式,用match进行匹配
            # 如果匹配上,将叶子节点的open置为true,并将叶子节点的父节点id进行赋值
            import re
            if re.match(item[url], self.current_url):
                item[open] = True
                open_left_parent_id = item[parent_id]

        # 设置一个菜单空字典
        menu_dict = {}

        # 将菜单列表转换为字典,并增加child,status,open字段
        # 将列表中的id作为key,列表中的值作为值
        for item in self.menu_list:
            item[child] = []
            item[status] = False
            item[open] = False
            menu_dict[item[id]] = item

        # 循环叶子字典,设置菜单字典中对应的child内容为叶子字典的值
        for k, v in menu_leaf_dict.items():
            menu_dict[k][child] = v
            # 设置菜单字典的parent_id的值为叶子字典的key(也就是叶子中的parent)
            parent_id = k
            # 设置菜单字典中的status状态为True,并循环设置父级菜单的status为True
            while parent_id:
                menu_dict[parent_id][status] = True
                parent_id = menu_dict[parent_id][parent_id]

        # 判断叶子父级id,将open设置为True,并循环设置父级菜单的open为True
        while open_left_parent_id:
            menu_dict[open_left_parent_id][open] = True
            open_left_parent_id = menu_dict[open_left_parent_id][parent_id]

        # print(‘循环权限用户url字典,将用户权限取得的id匹配菜单列表id并设置["child"]值为用户权限内容‘)
        # print(‘设置parent_id变量为:用户权限url的id‘)
        # print(‘如果有,菜单id的["status"]设置为True‘)
        # print(‘并且将parent_id的值设置为:菜单字典中菜单id的["parent"],等待下一次循环‘)
        # for k, v in menu_dict.items():
        #     print(k, v)
        # #####################处理菜单的等级关系#########################
        # menu_dict 应用:多级评论,多级菜单

        result = []
        # 按父子关系,将菜单列表中的值,层叠放入一个result中
        # 这里需要注意的是,只需要寻找一层的父id,并将自己放入,无需一层一层寻找到上一层的父节点。
        for row in menu_dict.values():
            if not row[parent_id]:
                result.append(row)
            else:
                menu_dict[row[parent_id]][child].append(row)

        return result

    def menu_content(self, child_list):

        response = ‘‘
        tpl = """
            <div class="item %s">
                <div class="title">%s</div>
                <div class="content">%s</div>
            </div>
        """
        for row in child_list:
            if not row[status]:
                continue
            active = ‘‘
            if row[open]:
                active = active
            if url in row:
                response += <a class="%s" href="%s">%s</a> % (active, row[url], row[caption])
            else:
                title = row[caption]
                content = self.menu_content(row[child])
                response += tpl % (active, title, content)

        return response

    def menu_tree(self):
        response = ‘‘
        tpl = """
            <div class="item %s">
                <div class="title">%s</div>
                <div class="content">%s</div>
            </div>
        """
        for row in self.menu_data_list():
            if not row[status]:
                continue
            active = ‘‘
            if row[open]:
                active = active
            title = row[caption]
            content = self.menu_content(row[child])
            response += tpl % (active, title, content)

        return response

    def action(self):
        """
        检查当前用户是否对当前URL有访问权,并获取对当前URL有什么权限
        :return:
        """

        action_list = []

        for k, v in self.permission2action_dict.items():
            if re.match(k, self.current_url):
                action_list = v
                break

        return action_list

 

五、index

index使用了一个装饰器,判断用户session中是否有用户信息,如果有用户信息,使用MenuHelper类实例化一个对象,调用对象的action方法,获得action_list

如果列表为空则返回无权访问

否则返回菜单树(调用了类的menu_data_list方法,循环递归的生成菜单树,返回的是后台生成的html代码

以及权限列表

最后通过权限列表中的内容分别进行操作,并返回至前台(这个位置没有编写完成,仅仅写了一个举例)

def permission(func):
    def inner(request, *args, **kwargs):
        user_info = request.session.get(user_info)
        if not user_info:
            return redirect(/login.html)
        obj = MenuHelper(request, user_info[username])
        action_list = obj.action()
        if not action_list:
            return HttpResponse(无权限访问)
        kwargs[menu_string] = obj.menu_tree()
        kwargs[action_list] = action_list
        return func(request, *args, **kwargs)
    return inner


@permission
def index(request, *args, **kwargs):
    actions_list = kwargs.get(actions_list)
    menu_string = kwargs.get(menu_string)
    if "GET" in actions_list:
        result = models.User.objects.all()
    else:
        result = []
    return render(request, index.html, {
        menu_string: menu_string,
        actions_list: actions_list,
        result: result,
    })

 

六、login.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="POST">
        {% csrf_token %}
        <input type="text" name="username" />
        <input type="text" name="pwd" />
        <input type="submit" value="提交" />
    </form>
</body>
</html>

 

 

 

七、index.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .content{
            margin-left: 20px;
            display: none;
        }
        .content a{
            display: block;
        }
        .active > .content{
            display: block;
        }
    </style>
</head>
<body>
    <div style="float: left;width: 20%;">
        {{ menu_string|safe }}
    </div>
    <div style="float: left;width: 80%;">
    </div>

</body>
</html>

 

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

基于Flask实现后台权限管理系统 - 汇总

基于Flask实现后台权限管理系统 - 表设计

网站后台系统权限部分实现

Java 之SpringBoot+SpringSecurity+Vue实现后台管理系统的开发三系统权限

ASP.NET如何制作后台权限管理

[开源]如何使用goapp写你的后台管理系统_golang