天知、地知、我知、你不知 的 权限问题
今天和大家聊聊权限的问题!各位在工作或是学习的过程中,肯定会遇到有关权限分配问题!怎么分配,怎么去开放接口,怎么让权限管理更顺从心意,这些都是头疼的问题!我在使用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,})
<!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), #权限菜单关系页面 ]
由于自定义中间件的认证方法,所以要想让认证生效就必须在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】。

浙公网安备 33010602011771号