• 基于角色的权限管理


     

    权限组件

    在我们写项目时,可能会遇到给不同的用户分配不同的权限的情况,那么什么是权限呢?权限其实就是一个url

    不同的url代表不同的功能,限定用户能访问的url,就给了用户不同的权限

    权限管理在很多项目中都有用到,所以我们可以讲权限管理的逻辑写成一个组件

    使它在不同的项目中只要经过一定的修改就能使用

    创建项目

    在一个项目中可以包含多个组件,同样一个组件也可以用于多个项目,这里我们想创建一个项目

    然后创建两个app,一个app01写项目的主逻辑,一个rbac(Role-Based Access Control)写权限相关的逻辑

    建表

    我们首先能想到的是用户和权限表,一个用户可以有多个权限,而一个权限也可以对应多个用户,这样他们就是多对多的关系

    但是这样的话,我们会发现一个问题,比如一个公司有很多销售员,这些销售员都有同样的权限,这时我们就需要添加很多重复的信息

    这里我们可以再添加一张角色表,让角色和用户、权限都有多对多的关系

    这样有新的用户后,只要给该用户分配一个角色就行了,而该角色又拥有相应的权限,就不需要再添加重复的信息了

    rbac.models

    复制代码
    from django.db import models
    
    # Create your models here.
    
    
    class UserInfo(models.Model):
        name = models.CharField(max_length=32)
        pwd = models.CharField(max_length=32, default=123)
        email = models.EmailField()
        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):
        url = models.CharField(max_length=32)
        title = models.CharField(max_length=32)
    def __str__(self):
            return self.title
    复制代码

    表建好后我们就要往表中添加数据了,这里我们可以先用admin添加一些基本数据

    rbac.admin

    复制代码
    from django.contrib import admin
    from .models import *
    # Register your models here.
    
    
    admin.site.register(UserInfo)
    admin.site.register(Role)
    
    
    class PermissionConfig(admin.ModelAdmin):
        list_display = ["id", "title", "url"]
        ordering = ["id"]
    admin.site.register(Permission, PermissionConfig)
    复制代码

    一般情况下,我们添加一条数据会看到一个数据对象,如果想要展示具体的id,title等属性,我们可以使用上面的方法,创建一个新的类继承(admin.ModelAdmin),在里面定义list_display,讲想要看到的内容写入列表,然后使用admin.site.register(Permission, PermissionConfig),这样我们就可以在页面上看到如下效果

    这里我们添加了8个权限,我们可以发现这8个权限可以分为两组,前4个是和用户有关的,而后4个是和订单有关的

    这里我们可以再创建一张权限组的表,使他和权限表一对多关联,同时给权限表再创建一个编号

    rbac.models

    复制代码
    from django.db import models
    
    # Create your models here.
    
    
    class UserInfo(models.Model):
        name = models.CharField(max_length=32)
        pwd = models.CharField(max_length=32, default=123)
        email = models.EmailField()
        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):
        url = models.CharField(max_length=32)
        title = models.CharField(max_length=32)
        permission_group = models.ForeignKey("PermissionGroup", default=1)
        code = models.CharField(max_length=32, default="")
    
        def __str__(self):
            return self.title
    
    
    class PermissionGroup(models.Model):
        caption = models.CharField(max_length=32)
    
        def __str__(self):
            return self.caption
    复制代码

    rbac.admin

    复制代码
    from django.contrib import admin
    from .models import *
    # Register your models here.
    
    
    admin.site.register(UserInfo)
    admin.site.register(Role)
    admin.site.register(PermissionGroup)
    
    
    class PermissionConfig(admin.ModelAdmin):
        list_display = ["id", "title", "url", "permission_group", "code"]
        ordering = ["id"]
    admin.site.register(Permission, PermissionConfig)
    复制代码

    最后添加的权限如下

    用户登录

     urls

    复制代码
    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^login/', views.login),
        url(r'^users/', views.users),
        url(r'^orders/', views.orders),
        url(r'^orders/add/', views.orders_add),
    ]
    复制代码

    这里我们为一些相关权限也设置了url

    视图函数

    由于这些是关于项目的逻辑,所以视图函数写在app01.views

    复制代码
    from django.shortcuts import render, redirect, HttpResponse
    from rbac import models
    # Create your views here.
    
    
    def login(request):
        if request.method == "GET":
            return render(request, "login.html")
        else:
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
            user = models.UserInfo.objects.filter(name=user, pwd=pwd).first()
            if user:
                # 验证成功之后
                request.session["user_id"] = user.pk
                # 当前登录用户的所有权限
                from rbac.service.initial import initial_session
                initial_session(request, user)
                return HttpResponse("登录成功")
            else:
                return redirect("/login/")
    
    
    def users(request):
    
        return HttpResponse("用户列表")
    
    
    def orders(request):
        permission_dict = request.session.get("permission_dict")
        return render(request, "orders.html", locals())
    
    
    def orders_add(request):
    
        return HttpResponse("添加订单")
    复制代码

    login.html

    复制代码
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>登录</title>
    </head>
    <body>
    
    <form action="/login/" method="post">
        {% csrf_token %}
        <p>用户名 <input type="text" name="user"></p>
        <p>密码 <input type="password" name="pwd"></p>
        <input type="submit" value="提交">
    
    </form>
    </body>
    </html>
    复制代码

    可以看到当用户登录以后我们先验证账号密码是否正确,然后写session,这里我们调用了rbac内的方法

    from rbac.service.initial import initial_session
    initial_session(request, user)

    这个方法究竟做了什么呢

    复制代码
    def initial_session(request, user):
        # 方式1
        # permission_info = user.roles.all().values("permissions__url", "permissions__title").distinct()
        # temp = []
        # for i in permission_info:
        #     temp.append(i["permissions__url"])
        # request.session["permission_list"] = temp
        # 方式2
        # 创建一个数据格式:包含所有权限,权限所在组,权限的编号
        permission_info = user.roles.all().values("permissions__url", "permissions__code", "permissions__permission_group_id").distinct()
    
        permission_dict = {}
        for permission in permission_info:
            if permission["permissions__permission_group_id"] in permission_dict:
                permission_dict[permission["permissions__permission_group_id"]]["urls"].append(
                    permission["permissions__url"])
                permission_dict[permission["permissions__permission_group_id"]]["codes"].append(
                    permission["permissions__code"])
            else:
                permission_dict[permission["permissions__permission_group_id"]] = {
                    "urls": [permission["permissions__url"]],
                    "codes": [permission["permissions__code"]]
                }
        '''
        permission_dict = {
            1:{},
            2:{
                "urls": [],
                "codes": []
            }
        }
        '''
        request.session["permission_dict"] = permission_dict
    复制代码

    我们可以看到这个方法有两种定义session数据的方式,方式1只是简单的取出数据库中用户拥有权限的url,并添加到一个列表中

    而方式2定义了一个如注释中所见的数据形式,将相关数据写入session中后,当用户访问一个url时,我们就可以从session中取出该用户拥有的权限,再验证一下访问的url是否在用户的权限中,如果在,那么就让他通过,不在则返回无权访问

    我们发现,不论用户访问哪个url我们都应该做权限的验证,这时就需要使用中间件来解决验证问题了

    中间件

    复制代码
    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import render, redirect, HttpResponse
    import re
    
    
    class M1(MiddlewareMixin):
    
        def process_request(self, request):
            current_path = request.path_info
            # 白名单,当用户访问以下url时直接通过
            valid_url_menu = ["/login/", "/reg/", "/admin/.*"]
            for valid_url in valid_url_menu:
                ret = re.match(valid_url, current_path)
                if ret:
                    return None
            # 方式1
            # permissions_list = request.session.get("permission_list")
            # 方式2
            permission_dict = request.session.get("permission_dict")
            if not request.session.get("user_id"):
                return redirect("/login/")
    
            for item in permission_dict.values():
                regs = item["urls"]
                codes = item["codes"]
                for reg in regs:
                    reg = "^%s$" % reg
                    ret = re.match(reg, current_path)
                    if ret:
                        request.permission_codes = codes
                        return None
            return HttpResponse("无权访问")
            # 方式1
            # flag = False
            # for permission_url in permissions_list:
            #     permission_url = "^%s$" % permission_url
            #     ret = re.match(permission_url, current_path)
            #     if ret:
            #         flag = True
            #         break
            # if not flag:
            #     return HttpResponse("无权访问")
    复制代码

    当用户访问时,先取到用户访问的url:request.path_info

    有些url我们应该让每个用户都能访问,比如登录、注册页面等

    所以我们设置一个白名单,如果用户访问的url在白名单内,则直接通过

    如果不在,我们从request.session中取到用户相关的权限,根据我们设置的数据类型,验证用户访问的url是否在权限内

    这里要注意,上面我们提到权限是一个url,这里我们还要补充一下,权限是一个包含正则的url,所以在验证是,我们用到了re模块进行正则匹配,同时为了配置的精准,我们在权限前后分别加上了^和$符

    当匹配成功后我们将url对应的code列表存到request中,并在中间件中放行

    页面上数据的使用

    如果用上面的方式1存数据,那么我们能拿到的只是一个url的列表,其中还包含正则表达式,在页面上使用时我们不能使用正则匹配,所以无法进行判断

    所以我们使用方式2的数据

    视图函数

    def orders(request):
        permission_dict = request.session.get("permission_dict")
        permission_codes = request.permission_codes
        per = Permissions(permission_codes)
        return render(request, "orders.html", locals())

    这里的Permissions是我们自己定义的一个类,类中定义了判断增删改查是否在permission_codes中的方法,方便我们在前端使用

    复制代码
    class Permissions(object):
        def __init__(self, code_list):
            self.code_list = code_list
    
        def list(self):
            return "list" in self.code_list
    
        def add(self):
            return "add" in self.code_list
    
        def delete(self):
            return "delete" in self.code_list
    
        def edit(self):
            return "edit" in self.code_list
    复制代码

    前端页面

    复制代码
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Title</title>
        <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
    </head>
    <body>
    <h3>订单列表</h3>
    <div class="col-md-6">
        {% if per.add %}
            <p><a href="/orders/add/"><button class="btn btn-primary pull-right">添加订单</button></a></p>
        {% endif %}
    
        <table class="table table-stripped">
        <tr>
            <th>订单号</th>
            <th>订单日期</th>
            <th>商品名称</th>
            <th>操作</th>
        </tr>
        <tr>
            <td>12343</td>
            <td>2012-12-12</td>
            <td>li</td>
            {% if per.delete %}
                <td><a href=""><button class="btn btn-danger btn-sm">删除</button></a></td>
            {% endif %}
    
        </tr>
    </table>
    </div>
    
    </body>
    </html>
    复制代码

    前端我们只需要使用per这个对象的方法就能直接判断当前用户有没有对应的权限,从而进行页面渲染

  • 相关阅读:
    Kubernetes1.91(K8s)安装部署过程(一)--证书安装
    开源仓库Harbor搭建及配置过程
    有关centos7 图形化root用户登录
    linux服务器查看tcp链接shell
    django表格form无法保存评论排查步骤
    Redis 4.x 安装及 发布/订阅实践和数据持久化设置
    django博客项目-设置django为中文语言
    windows 环境下如何使用virtualenv python环境管理工具
    【转载】python中利用smtplib发送邮件的3中方式 普通/ssl/tls
    php安装phpize工具
  • 原文地址:https://www.cnblogs.com/QQ279366/p/8525355.html
Copyright © 2020-2023  润新知