CRM项目之权限控制1

学自:https://www.cnblogs.com/wupeiqi/articles/9178982.html

权限系统介绍

在一个公司有不同的等级,不同等级看到的信息是不同的,这就是权限。如一个项目下的资料只能该项目中成员能看,其他项目组人员不能查看(没有权限)。

在web中什么是权限?
一个url就是一个权限。一个url就是一个功能,有没有权限去访问这个功能。权限就是将某个url分配给某个人。

权限表结构设计

第一版

用户表:id | name
权限表:id | url
基于用户设计表结构,每个人的不同权限是url的访问权限。一个人有多个权限,一个权限对应多个人,所以两张表是多对多关系。
用户权限关系表: 用户id | 权限id

  1. 问题一:当表中已经有一系列的用户(A销售人员,B销售主管,C经理)和其对应的权限(每个用户是个权限),当在增加一个经理人员时,对第三张表的操作?
    答:经理C有十条权限,增加经理D时,需要在第三张表中插入十条数据,即经理D和其权限对应表。
  2. 问题二:给表中某一类人增加权限时,即给所有的销售人员(100个人)增加三条权限,对第三张关系表的操作?
    答:需要拿到用户表中每个人的id,再插入3条记录到第三张关系表。需要做100次查询,300次插入,操作比较繁琐。

第一版存在的缺点是:给某一类人增加权限时需要先查找再给每个人增加权限,某一类增加新成员时,需要将这类人的所有权限重新插入,给数据库的压力很大。

第二版

设计思路是,将人进行划分、分类,给人分配角色,基于角色的权限设计。

表结构:
权限表 id | name
权限角色表 权限id | 角色id
角色表 id | 角色
用户角色表 角色 id | 用户id
用户表 id | 用户

表关系:
权限表–角色表 多对多
用户表–角色表 多对多

解决了第一版中的问题。

ORM 模型类代码

from django.db import models


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

    def __str__(self):
        return self.title


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

    def __str__(self):
        return self.title


class UserInfo(models.Model):
    """
    用户表
    """
    name = 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)

    def __str__(self):
        return self.name

基于admin权限信息录入

现有的url:

客户管理
	客户列表:/customer/list/
	添加客户:/customer/add/
	删除客户:/customer/list/(?P<cid>\d+)/
	修改客户:/customer/edit/(?P<cid>\d+)/
	批量导入:/customer/import/
	下载模板:/customer/tpl/
账单管理
	账单列表:/payment/list/
	添加账单:/payment/add/
	删除账单:/payment/del/(?P<pid>\d+)/
	修改账单:/payment/edit/<?P<pid>\d+/

在权限组件中录入相关信息:

录入权限
创建用户
创建角色
用户分配角色
角色分配权限

这么一来,用户登录时,就可以根据自己的【用户】找到所有的角色,再根据角色找到所有的权限,再将权限信息放入session,以后每次访问时候需要先去session检查是否有权访问。

录入流程:

  1. 创建超级用户:python manage.py createsuperuser
  2. 在rbac的admin中添加
    admin.site.register(models.Permission)
    admin.site.register(models.UserInfo)
    admin.site.register(models.Role)
    
  3. 访问admin后台管理系统http://127.0.0.1:8000/admin 录入权限信息。
  4. 录入人员信息和角色信息进行权限分配。

快速实现简单的权限控制

设计思路

  1. 登录界面是否有权限访问。
  2. POST请求,检查用户登录是否合法。
  3. 用户第一次登陆后获取用户的所有权限放入session中。
  4. 再次向服务器发起请求,后端编写中间件对当前用户访问的url进行权限判断(是否在session中)。

代码编写

获取用户权限放入session中

重点是对当前用户所有权限获取的思考。

def login(request):
    if request.method == "GET":
        return render(request, 'login.html')
    name = request.POST.get('username')
    pwd = request.POST.get('pwd')

    current_user = models.UserInfo.objects.filter(name=name, password=pwd).first()
    if not current_user:
        return render(request, 'login.html', {'msg': '用户名或者密码错误'})

    '''
    1. 获取用户当前权限
        1.1 current_user.roles.all().values("permissions__id", "permissions__id")
        此时获取的是有bug的。一个人有多个角色,多个角色有相同的权限时,拿到的权限中有重复的url(三个角色中都有A权限,每个角色中都拿了一次A
        权限,相当于获取的内容中有3个A权限对应的url),所以需要去重。
        1.2 current_user.roles.all().values("permissions__id", "permissions__url").distinct()
        1.3 此时还有一个小问题,当我们给插入一个人角色,没有分配权限时(权限为空),我们取某个用户时,就会出现拿到权限为空的场景,空值不是我们需要
        的,这种情况得排除掉。
        有一个场景,插入一个'金牌销售'的角色,没有给分配任何权限,在Roles中就会有这样一条记录:
            title           permissions
          '金牌讲师'            null
        current_user.roles.all().values("permissions__id", "permissions__url").distinct() 拿到的结果中有一条记录:
            permissions__id   permissions__id
              null               null
        这并符合我们的需求,我们需要拿到的时该用户所有的可用权限,这条记录应该排除掉
        current_user.roles.filter(permissions__isnull = False).values("permissions__id", "permissions__url").distinct()
    '''
    # 当前用户所有权限
    permission_queryset = current_user.roles.filter(permissions__isnull=False).values("permissions__id", "permissions__url").distinct()

    # 获取权限中的url
    permission_list = [item.get('permissions__url') for item in permission_queryset]
    print(permission_queryset)

    # 放入session中
    request.seesion['permission_url_list'] = permission_list

    return redirect('/custom/list/')

简易中间件权限校验

在中间件中对所有的请求进行拦截检测,检测当前登录用户访问的url是否在已拥有的权限url中。
重点:mindware中间件流程,自定义中间件,re正则权限匹配,引出白名单的思考,importlib模块的了解。

实现步骤:
在这里插入图片描述

# my_md.py
import re

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse


class PermisionControl(MiddlewareMixin):

    def process_request(self, request):
        '''
        1. 拿到当前用户请求的url
        2. 获取当前用户在session中保存的权限列表
        3. 权限信息匹配
        '''
        current_url = request.path_info
        # 有一些权限是所有人默认都有的,不需要做权限判断,先进行一个白名单判断,如果是白名单url,就不用再走权限判断了
        valid_url_list = ['/login/']  # 白名单里可以设置,完整字符串,正则(以什么开头,以什么结尾)
        for valid_url in valid_url_list:
            if re.match(valid_url, current_url):
                # 白名单中的url,无需权限验证
                # 返回None,继续走后续步骤
                return None


        permission_list = request.session.get('permission_url_list')
        if not permission_list:
            # 返回 HttpResponse 不走 后续步骤,直接返回到页面
            return HttpResponse('未获取到用户权限信息')

        print(current_url)
        print(permission_list)

        flag = False
        for url in permission_list:
            regx = '^{}$'.format(url)
            if re.match(regx, current_url):
                flag = True
                break

        if not flag:
            return HttpResponse('无权访问')

权限控制代码改进(将权限相关的功能放到rbac)

用户登录和权限初始化功能拆分

在视图函数login中,功能可以分为两个部分,用户登录权限信息初始化。权限信息初始化功能是一个通用的功能,可以将其提取出来,到我们的rbac部分,以后不用写重复代码。

  1. 新建py文件
    在这里插入图片描述
    2.代码
    init_permission.py
def init_permission(current_user, request):
    '''
    初始化权限信息
    :param current_user: 当前登录用户
    :param request: 请求相关参数
    :return:
    '''
    # 当前用户所有权限
    permission_queryset = current_user.roles.filter(permissions__isnull=False).values("permissions__id", "permissions__url").distinct()

    # 获取权限中的url
    permission_list = [item.get('permissions__url') for item in permission_queryset]

    # 放入session中
    request.session['permission_url_list'] = permission_list

account.py

from django.shortcuts import render, redirect, HttpResponse
# 最好不要从其他app中导入模型类,这是不规范的写法
from rbac import models
from rbac.service.init_permission import init_permission


def login(request):
    if request.method == "GET":
        return render(request, 'login.html')
    name = request.POST.get('username')
    pwd = request.POST.get('pwd')

    current_user = models.UserInfo.objects.filter(name=name, password=pwd).first()
    if not current_user:
        return render(request, 'login.html', {'msg': '用户名或者密码错误'})

    init_permission(current_user, request)

    return redirect('/customer/list/')

session中key名字使用的思考

在我们上面的代码中session的key是直接使用字符串写的,我们任何一处使用这个key的地方都需要来init_permission中拷贝一份,这到问题不大,我们复制粘贴就好了。麻烦的是需求某一天变了,key的内容修改称其他值,就要找到项目中所有的key字符串进行替换。所以我们将sessino的key写入到配置文件中,后续修改一个地方就可以了。

在settings.py中写入

PERMISSION_SESSION_KEY = 'permission_url_list'

将使用字符串的地方替换为使用配置文件中的常量

from django.conf import settings
# 替换代码
permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
request.session[settings.PERMISSION_SESSION_KEY] = permission_list

自定义中间件移到rbac

自定义中间件也是一个通用的功能,我们将其从web app中移植到rbac中。
在这里插入图片描述

rbac.py文件

import re

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from django.conf import settings

class RbacMiddleware(MiddlewareMixin):
    def process_request(self, request):
        '''
        1. 拿到当前用户请求的url
        2. 获取当前用户在session中保存的权限列表
        3. 权限信息匹配
        '''
        current_url = request.path_info
        # 有一些权限是所有人默认都有的,不需要做权限判断,先进行一个白名单判断,如果是白名单url,就不用再走权限判断了
        valid_url_list = ['/login/']  # 白名单里可以设置,完整字符串,正则(以什么开头,以什么结尾)
        for valid_url in valid_url_list:
            if re.match(valid_url, current_url):
                # 白名单中的url,无需权限验证
                # 返回None,继续走后续步骤
                return None

        permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
        if not permission_list:
            # 返回 HttpResponse 不走 后续步骤,直接返回到页面
            return HttpResponse('为获取到用户权限信息')

        print(current_url)
        print(permission_list)

        flag = False
        for url in permission_list:
            regx = '^{}$'.format(url)
            if re.match(regx, current_url):
                flag = True
                break

        if not flag:
            return HttpResponse('无权访问')

settings.py文件

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'rbac.middlewares.rbac.RbacMiddleware',
]

中间件中白名单的思考

在中间件中需要获取白名单,现在我们是写死的(不能写死在代码中),白名单需要使用到rabc组件的用户来定义,我们需要将其写入到配置文件中,从配置文件中拿白名单。
valid_url_list = settings.VALID_URL_LIST

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值