天知、地知、我知、你不知 的 权限问题

今天和大家聊聊权限的问题!各位在工作或是学习的过程中,肯定会遇到有关权限分配问题!怎么分配,怎么去开放接口,怎么让权限管理更顺从心意,这些都是头疼的问题!我在使用Django设计解决有关权限问题的时候,也是懵的一逼。因为你不知道要从哪里下手,要怎么分配权限!

  既然无法想通权限怎么分配的问题,我就开始了山南海北,天马行空的瞎想,不知怎么想到了公司的结构上,也就是我一个小IT搬砖员,上边有组长,组长上边有部长,经理啥的,再往上就是各位老总,我何不按照公司这种部门和职位分配作为切入口呢?我们每个人都是这个公司的员工,只是大家的职位不同;不同职位有不同的权限;我按照这个思路就开始了我的权限设计的生涯![后来做完之后,感觉自己那天的脑子简直聪明的不要不要的!]

  根据这个思路我设计了一套用户权限分配的数据库表;权限与菜单关系表;但是当我设计用户访问的时候又遇到了难题!因为这个权限设计出来之后,全公司的人都要进行访问,这个访问的url我怎么分配,怎么判断某人访问的时候他就有这个权限?后来琢磨的是根据职位(角色)不同,给他们开放不同的访问接口,我在视图函数中对这些访问进行判断以确定时候可以访问!再确定思路之后,我突然想起django框架有中间件的这种模块,主要功能就是用于访问时的拓展开发,我为什么不把访问请求的认证放到中间件中,每人来登录我的系统我就直接对他进行认证操作!直接返回给他一个可以操作的权限页面,这样大家都方便!

  有权限你就玩,没权限你就歇菜!主要是你有哪些权限还不是我说了算!天知、地知、我知、你不知的权限分配都在我的掌控之中!具体代码如下:

from django.db import models

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

    def __str__(self):
        return self.username

class Role(models.Model):
    """
    角色表
    """
    caption = models.CharField(verbose_name='角色', max_length=32)

    def __str__(self):
        return self.caption

class User2Role(models.Model):
    """
    用户角色关系表
    """
    user = models.ForeignKey(User, verbose_name='用户', related_name='roles')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='users')

    def __str__(self):
        return '%s-%s' % (self.user.username, self.role.caption,)

class Menu(models.Model):
    """
    菜单表
    """
    caption = models.CharField(verbose_name='菜单名称', max_length=32)
    parent = models.ForeignKey('self', verbose_name='父菜单', related_name='p', null=True, blank=True)

    def __str__(self):
        prev = ""
        parent = self.parent
        while True:
            if parent:
                prev = prev + '-' + str(parent.caption)
                parent = parent.parent
            else:
                break
        return '%s-%s' % (prev, self.caption,)

class Permission(models.Model):
    """
    权限
    """
    caption = models.CharField(verbose_name='权限', max_length=32)
    url = models.CharField(verbose_name='URL正则', max_length=128)
    menu = models.ForeignKey(Menu, verbose_name='所属菜单', related_name='permissions',null=True,blank=True)

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

class Action(models.Model):
    """
    操作:增删改查
    """
    caption = models.CharField(verbose_name='操作标题', max_length=32)
    code = models.CharField(verbose_name='方法', max_length=32)

    def __str__(self):
        return self.caption

class Permission2Action2Role(models.Model):
    """
    角色权限操作关系表
    """
    permission = models.ForeignKey(Permission, verbose_name='权限URL', related_name='actions')
    action = models.ForeignKey(Action, verbose_name='操作', related_name='permissions')
    role = models.ForeignKey(Role, verbose_name='角色', related_name='p2as')

    class Meta:
        unique_together = (
            ('permission', 'action', 'role'),
        )

    def __str__(self):
        return "%s-%s-%s" % (self.permission, self.action, self.role,)
数据库表设计--->用户角色权限分配表
from django.shortcuts import render,HttpResponse,redirect
from app02 import models
import re
# Create your views here.

def purview(request):
    v = models.Role.objects.filter(users__user__username="root")\
        .values("p2as__permission__url","p2as__permission__caption","p2as__action__code").distinct()
    # print(v) # 获取当前用户的所有权限及对应的操作
    permission_dict = {}
    for row in v:
        if row["p2as__permission__url"] in permission_dict:
            permission_dict[row["p2as__permission__url"]].append(row["p2as__action__code"])
        else:
            permission_dict[row["p2as__permission__url"]] = [row["p2as__action__code"],]
    print(permission_dict)
    return HttpResponse("ok")

def logins(request):
    if request.method == "GET":
        return render(request,"logins.html")
    else: #用户登录成功,获取其权限及可以完成的操作
        username = request.POST.get("user")
        password = request.POST.get("password")
        user = models.User.objects.filter(username=username,password=password).first()

        v = models.Role.objects.filter(users__user=user) \
            .values("p2as__permission__url", "p2as__permission__caption", "p2as__action__code").distinct()
        # print(v) # 获取当前用户的所有权限及对应的操作
        permission_dict = {}  #定义一个新的字典,把数据整合成{"url":['操作方法1','操作方法2','操作方法3',]}
        for row in v:   #整合数据
            if row["p2as__permission__url"] in permission_dict:
                permission_dict[row["p2as__permission__url"]].append(row["p2as__action__code"])
            else:
                permission_dict[row["p2as__permission__url"]] = [row["p2as__action__code"], ]
        request.session["permission_dict"]=permission_dict  #把整合好的数据写入session
        request.session.set_expiry(600)  #这是session的超时时间
        return HttpResponse("登录成功!")

def index(request,*args,**kwargs):
    return HttpResponse("登录成功,并有权限访问")

def menu(request):
    """
    权限与菜单之间的关系
    先处理权限,再处理菜单,由内到里
    需要用户名或用户ID,产出:用户关联所有菜单
    :param request:
    :return:
    """
    # 所有菜单:处理成当前用关联的菜单
    all_menu_list = models.Menu.objects.all().values('id','caption','parent_id')
    """
    [
        {'id':1, 'caption':'菜单1', parent_id:None},
        {'id':2, 'caption':'菜单2', parent_id:None},
        {'id':3, 'caption':'菜单3', parent_id:None},
        {'id':4, 'caption':'菜单1-1', parent_id:1},
    ]

    {
        1:{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[]},
        2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
        3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
        5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
    }
   """
    #通过session获取当前登录用户的信息
    #拿到当前用户所有的权限以及关联的菜单ID
    user = models.User.objects.filter(id=1).first()
    role_list = models.Role.objects.filter(users__user=user)
    permission_list = models.Permission2Action2Role.objects.filter(role__in=role_list)\
        .values('permission__id','permission__url','permission__menu_id','permission__caption').distinct()
    """
    [
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 2 },
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 3 },
        {'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 4 },
    ]
    """
    ##### 将权限挂靠到菜单上 ########
    all_menu_dict = {}   #先整合挂靠权限的菜单表结构
    for row in all_menu_list:
        row['child'] = []      # 添加孩子
        row['status'] = False # 是否显示菜单
        row['opened'] = False # 是否默认打开
        all_menu_dict[row['id']] = row  #将菜单id为key,对应的菜单信息为values

    #对当前用户的权限进行整合
    for per in permission_list:
        if not per['permission__menu_id']:
            continue
        #对数据也整和成一个新的字典
        item = {
            'id':per['permission__id'],   #权限id
            'caption':per['permission__caption'],   #权限名字
            'parent_id':per['permission__menu_id'], #权限所属菜单
            'url': per['permission__url'],   #该权限对应的url
            'status': True,     #默认显示
            'opened': False     #默认不打开
        }
        if re.match(per['permission__url'],request.path_info):  #对用户访问的网址路由做匹配,只有匹配上才会改值
            item['opened'] = True  #默认打开
        pid = item['parent_id']   #父id
        all_menu_dict[pid]['child'].append(item)   #构造新的结构,如果有父id则其孩子中加入孩子的所有信息

        # 将当前权限前辈status=True   全部显示
        temp = pid # 1.父亲ID
        while not all_menu_dict[temp]['status']:
            all_menu_dict[temp]['status'] = True
            temp = all_menu_dict[temp]['parent_id']
            if not temp:
                break

        # 将当前权限前辈opened=True   默认全部展开
        if item['opened']:
            temp1 = pid # 1.父亲ID
            while not all_menu_dict[temp1]['opened']:
                all_menu_dict[temp1]['opened'] = True
                temp1 = all_menu_dict[temp1]['parent_id']
                if not temp1:
                    break

    # ############ 处理菜单和菜单之间的等级关系 ############
    """
    all_menu_dict = {
        1:{'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 },]},
        2:{'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
        3:{'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},
        5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},
    }


    all_menu_list= [
        {'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[{'permission__url':'/order.html','permission__caption': '订单管理','permission__menu_id': 1 }, {'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},]},
        {'id':2, 'caption':'菜单2', parent_id:None,status:False,opened:False,child:[]},
        {'id':3, 'caption':'菜单3', parent_id:None,status:False,opened:False,child:[]},

    ]
    """
    result = []
    for row in all_menu_list:
        pid = row['parent_id']
        if pid:
            all_menu_dict[pid]['child'].append(row)
        else:
            result.append(row)


    ##################### 结构化处理结果 #####################
    # for row in result:
    #     print(row['caption'],row['status'],row['opened'],row) #查看结构化处理结果

    ##################### 通过结构化处理结果,生成菜单 #### 开始 #####################
        """数据整合结果
        result = [
            {'id':1, 'caption':'菜单1', parent_id:None,status:False,opened:False,child:[5:{'id':4, 'caption':'菜单1-1', parent_id:1,status:False,opened:False,child:[]},2:{'id':2, 'caption':'菜单2', parent_id:1,status:False,opened:False,child:[]},]}
            {'id':2, 'caption':'菜单2', parent_id:None,status:True,opened:False,child:[]},
            {'id':3, 'caption':'菜单3', parent_id:None,status:true,opened:False,child:[url...]},
        ]

        status=False ,不生产成
        opened=True  ,true不加,false,加hide
        目标样式,整合成HTML内容
        <div class='menu-item'>
            <div class='menu-header'>菜单1</div>
            <div class='menu-body %s'>
                <a>权限1</a>
                <a>权限2</a>
                 <div class='menu-item'>
                    <div class='menu-header'>菜单11</div>
                    <div class='menu-body hide'>
                        <a>权限11</a>
                        <a>权限12</a>
                    </div>
                </div>
            </div>
        </div>
        <div class='menu-item'>
            <div class='menu-header'>菜单2</div>
            <div class='menu-body hide'>
                <a>权限1</a>
                <a>权限2</a>
            </div>
        </div>
        <div class='menu-item'>
            <div class='menu-header'>菜单3</div>
            <div class='menu-body hide'>
                <a>权限1</a>
                <a>权限2</a>
            </div>
        </div>
        """
        #定义生成菜单函数
        def menu_tree(menu_list):
            tpl1 = """
                <div class='menu-item'>
                    <div class='menu-header'>{0}</div>
                    <div class='menu-body {2}'>{1}</div>
                </div>
            """ #菜单
            tpl2 = """
                <a href='{0}' class='{1}'>{2}</a>
                """ #权限a标签
            menu_str = ""
            for menu in menu_list:  #对整合好的数据进行循环
                if not menu['status']:   #判断 status状态,True默认展开,false默认不显示
                    continue
                # menu: 菜单,权限(url)
                if menu.get('url'):  #判断当前一个元素中有没有url这个字段,有证明是权限
                    # 权限
                    menu_str += tpl2.format(menu['url'], 'active' if menu['opened'] else "", menu['caption'])
                            #格式化字符串,传值:url,标签属性,权限名
                else:   #没有就是菜单
                    # 菜单
                    if menu['child']:  #判断菜单时候还有子菜单或权限 有的话就迭代继续处理
                        child_html = menu_tree(menu['child'])
                    else:   #没有就返回空
                        child_html = ""
                    menu_str += tpl1.format(menu['caption'], child_html, "" if menu['opened'] else 'hide')
                    #格式化字符串,传值:菜单名,子菜单或权限,标签属性值<通过字段中opened标志位判断>
            return menu_str   #将拼接完成之后的HTML标签返回。

        menu_html = menu_tree(result)

        ##################### 通过结构化处理结果,生成菜单 #### 结束 #####################
        return render(request,"menu.html",{"menu_html":menu_html,})
views 视图函数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="POST" action="/app02/logins.html">
    {% csrf_token %}
    <p><input type="text" name="user"></p>
    <p><input type="submit" value="提交"></p>
</form>
</body>
</html>
模版 ---> 简单登录页面(测试用)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
     <style>
        .hide{
            display: none;
        }
        .menu-body{
            margin-left: 20px;
        }
        .menu-body a{
            display: block;
        }
        .menu-body a.active{
            color: red;
        }
    </style>
</head>
<body>
{{ menu_html|safe }}
<script src="/static/js/jquery-3.2.1.js"></script>
<script>
$(function(){

    $('.menu-header').click(function(){
        $(this).next().removeClass('hide').parent().siblings().find('.menu-body').addClass('hide');
    })

})
</script>
</body>
</html>

menu.html
模版 ---> 简单权限菜单显示页面(测试用)
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
import re

class M1(MiddlewareMixin):
    def process_request(self,request):  #用户请求过程中进行过滤操作
        print(request.path_info)
        valid = ["/","/app02/logins.html","/app02/index.html"]   #定义白名单,允许某些url地址通过
        if request.path_info not in valid:      #如果写入的url没在白名单中,则执行匹配操作
            action = request.GET.get("md")      #获取用户发送的GET请求权限
            permission_dict = request.session.get("permission_dict") #从session获取用户登录之后的权限
            if not permission_dict:    #如果用户登录之后,没有信息,证明其没有操作权限
                return HttpResponse("无权限")  #通过中间件直接返回信息
            flag = False    #定义一个标志位
            for k,v in permission_dict.items():  #对从session中获取的字典进行循环
                if re.match(k,request.path_info):  #正则匹配url地址  正确的话往下执行
                    if action in v:   # 如果请求的方法在权限内
                        flag = True  #将标志位改成True
                        break       #结束循环,直接进入
            if not flag:    #对标志位进行判断,如果是False的话,返回无权限。
                return HttpResponse("无权限")
中间件 ---> 权限访问认证
from django.conf.urls import url
from app02 import views

urlpatterns = [
    url(r'^purview.html$',views.purview), #从数据库中读取权限
    url(r'^logins.html$',views.logins),  #登录路由
    url(r'^(\w+)-(\w+).html$',views.index),#用于权限判断
    url(r'^menu.html$',views.menu), #权限菜单关系页面
]
url ---> 路由系统

由于自定义中间件的认证方法,所以要想让认证生效就必须在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',
    
    'middleware.Authority.M1',#注册中间件
]
配置文件中注册中间件

具体的权限认证已经开发成一个公共组件!!!直接注册就能使用【开发环境:Django】。

posted @ 2018-03-07 21:38  dion至君  阅读(216)  评论(0)    收藏  举报