模型:rbac 基于角色的权限访问控制
什么是权限?
一个包含正则表达式的url就是权限html
class UserInfo(models.Model): name=models.CharField(max_length=32) pwd=models.CharField(max_length=32) roles=models.ManyToManyField("Role") def __str__(self): return self.name class Role(models.Model): title=models.CharField(max_length=32) permissions=models.ManyToManyField("Permission") def __str__(self): return self.title class Permission(models.Model): title=models.CharField(verbose_name="权限名称",max_length=32) url=models.CharField(max_length=32) def __str__(self): return self.title
url(r'^login/', views.LoginView.as_view()), url(r'^userinfo/$', views.UserView.as_view()), url(r'^userinfo/add', views.adduser), url(r'^userinfo/(\d+)/change/', views.change),
views:前端
from rbac.models import UserInfo from django.views import View class LoginView(View): def get(self,request): return render(request,"login.html") def post(self,request): user=request.POST.get("user") pwd=request.POST.get("pwd") #查询是否有这个帐户 user=UserInfo.objects.filter(name=user,pwd=pwd).first() #若是用户存在 if user: # 将当前登陆用户的权限列表注册到session中 request.session["user_id"]=user.pk permission_list = [] #查询这个用户全部的权限(经过用户-角色-权限),并去重 permissions=user.roles.all().values("permissions__url").distinct() for per in permissions: #将这个用户的权限添加到权限列表 permission_list.append(per.get("permissions__url")) print("permission_list: ",permission_list) #将权限列表写入session中 request.session["permission_list"]=permission_list return HttpResponse("OK")
settings正则表达式
"rbac.apps.RbacConfig"
中间件
rbac.pydjango
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse,render,redirect class PermissionValid(MiddlewareMixin): def process_request(self,request): #获取当前访问路径 current_path = request.path # /userinfo/add/ #设置白名单 white_list=["/login/","/admin/*"] #若是访问路径在白名单中则放行 for per in white_list: import re ret=re.search(per,current_path) if ret: return None #认证用户是否登陆 #从session中获取用户ID user_id=request.session.get("user_id") #若是用户没有登陆则跳转到登陆页面 if not user_id: return redirect("/login/") #校验权限(第一种方式) #从session中获取用户权限列表 permission_list = request.session["permission_list"] import re flag = False for permission in permission_list: # "/userinfo/" #肯定校验规则 permission = "^%s$" % permission ret = re.search(permission, current_path) if ret: return None return HttpResponse("您没有权限访问!")
在使用正则验证权限的时候要注意在肯定校验规则时别忘了在路径先后加上^和$session
校验包含三个部分:
白名单校验(正则)
登陆校验
权限校验数据结构
前端页面验证当前登陆用户是否有登陆权限,若是有就显示相应选项app
{% if '/userinfo/add/' in permission_list %} <a href="/userinfo/add/" class="btn btn-warning addbtn">添加用户</a> {% endif %} <table class="table table-bordered"> {% for user in user_list %} <tr> <td>{{ user.name }}</td> {% if "/userinfo/(\d+)/delete/" in permission_list %} <td><a href="/userinfo/{{ user.pk }}/delete/">删除</a></td> {% endif %} {% if "/userinfo/(\d+)/change/" in permission_list %} <td><a href="/userinfo/{{ user.pk }}/change/">编辑</a></td> {% endif %} </tr> {% endfor %} </table>
思考:上面的操做已经实现权限控制的需求,经过url来控制按钮的显示,可是判断权限使用的条件是写死的,例如/userinfo/add/,若是能让代码更通用更简洁?ide
变动数据结构:
添加PermissionGroup表,用于存储权限分组
权限表中添加一个action字段,里面保存add,delete之类的描述行为的信息
权限表中添加一个group字段,与PermissionGroup 表为一对多关系,好比用户表的增删改查权限全规到用户组,角色表的增删改查全归到角色组函数
class User(models.Model): name=models.CharField(max_length=32) pwd=models.CharField(max_length=32) 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): title=models.CharField(max_length=32) url=models.CharField(max_length=32) action=models.CharField(max_length=32,default="") group=models.ForeignKey("PermissionGroup",default=1) def __str__(self):return self.title class PermissionGroup(models.Model): title = models.CharField(max_length=32) def __str__(self): return self.title
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/$', views.users), url(r'^users/add', views.add_user), url(r'^users/delete/(\d+)', views.del_user), url(r'^roles/', views.roles), url(r'^login/', views.login), ]
将注册session功能单独提取出来,与rbac模块放到一块儿
perssions.pypost
def initial_session(user,request): #获取权限url、权限组id、权限action,并去重 permissions = user.roles.all().values("permissions__url","permissions__group_id","permissions__action").distinct() #将获取到的permissions数据从新组合,构建一个字典,变为咱们须要的数据格式 permission_dict={} for item in permissions: gid=item.get('permissions__group_id') if not gid in permission_dict: permission_dict[gid]={ "urls":[item["permissions__url"],], "actions":[item["permissions__action"],] } else: permission_dict[gid]["urls"].append(item["permissions__url"]) permission_dict[gid]["actions"].append(item["permissions__action"]) #将转换后的数据写到session中 request.session['permission_dict']=permission_dict
rbac.py
class ValidPermission(MiddlewareMixin): def process_request(self,request): #当前访问路径 current_path = request.path_info #检查是否属于白名单 valid_url_list=["/login/","/reg/","/admin/.*"] for valid_url in valid_url_list: ret=re.match(valid_url,current_path) if ret: return None #校验是否登陆 user_id=request.session.get("user_id") if not user_id: return redirect("/login/") #校验权限2 permission_dict=request.session.get("permission_dict") for item in permission_dict.values(): urls=item['urls'] for reg in urls: reg="^%s$"%reg ret=re.match(reg,current_path) if ret: #若是匹配成功,就给request添加一个actions request.actions=item['actions'] return None return HttpResponse("没有访问权限!")
from rbac.models import * #封装增删改查四种权限的判断语句,模板能够直接调用这个类下的属性便可 class Per(object): def __init__(self,actions): self.actions=actions def add(self): return "add" in self.actions def delete(self): return "delete" in self.actions def edit(self): return "edit" in self.actions def list(self): return "list" in self.actions def users(request): user_list=User.objects.all() #查询当前登陆人得名字 id=request.session.get("user_id") user=User.objects.filter(id=id).first() #调用上面的Per类,这样在模板中if per.delete就等同于 if "delete" in self.actions,让代码变得更简洁了 per = Per(request.actions) return render(request,"users.html",locals()) def add_user(request): return HttpResponse("add user.....") def del_user(request,id): return HttpResponse("del"+id) def roles(request): role_list=Role.objects.all() per = Per(request.actions) return render(request,"roles.html",locals()) from rbac.service.perssions import initial_session def login(request): if request.method=="POST": user=request.POST.get("user") pwd=request.POST.get("pwd") user=User.objects.filter(name=user,pwd=pwd).first() if user: ############################### 在session中注册用户ID###################### request.session["user_id"]=user.pk ###############################在session注册权限列表############################## #查询当前登陆用户的全部权限,注册到session中 initial_session(user,request) return HttpResponse("登陆成功!") return render(request,"login.html")
举例:
{% if per.add %} <a href="/users/add/" class="btn btn-primary">添加用户</a> {% endif %}
在上面initial_session中permissions获取到的是一个QuerySet,其格式为:
<QuerySet [ { 'permissions__url': '/stark/crm/customer/', 'permissions__group_id': 1, 'permissions__action': 'list'}, { 'permissions__url': '/stark/crm/customer/add/', 'permissions__group_id': 1, 'permissions__action': 'add'}, { 'permissions__url': '/stark/crm/customer/(\\d+)/change/', 'permissions__group_id': 1, 'permissions__action': 'edit'}, { 'permissions__url': '/stark/crm/customer/public/', 'permissions__group_id': 1, 'permissions__action': 'list'}, ]
咱们须要把它转换为咱们想要的格式,为此咱们须要构建一个字典:
{1: { 'urls': ['/stark/crm/customer/', '/stark/crm/customer/add/', '/stark/crm/customer/(\\d+)/change/', '/stark/crm/customer/public/', '/stark/crm/customer/mycustomer/' ], 'actions': ['list', 'add', 'edit', 'list', 'list'] } 2: { 'urls': ['/roles/'], 'actions': ['list']} } }
方法1:
#将获取到的permissions从新组合,变为咱们须要的数据格式 permission_dict={} for item in permissions: gid=item.get('permissions__group_id') if not gid in permission_dict: permission_dict[gid]={ "urls":[item["permissions__url"],], "actions":[item["permissions__action"],] } else: permission_dict[gid]["urls"].append(item["permissions__url"]) permission_dict[gid]["actions"].append(item["permissions__action"])
举例菜单栏在页面左侧,当前登陆用户有几个权限就应该显示几行内容,例如当前用户有查看学生信息的权限,那么菜单中就应该出现学生信息的连接,点击连接能够跳转到相应页面,所以是否显示某一个连接只须要判断当前用户对这个页面是否有list权限便可,那么如何作呢?
在上面的注册权限信息到session阶段,咱们构建了一个名为permission_dict的字典,但permission_dict并不方便用于菜单栏显示的判断,所以咱们在initial_session函数中再构建一个menu_permission_list列表或元祖来为咱们左侧菜单栏的权限判断作服务
代码:
一样在perssions.py的initial_session函数里面
#注册菜单权限 permissions = user.roles.all().values("permissions__url", "permissions__action", "permissions__title").distinct() menu_permission_list = [] for item in permissions: if item["permissions__action"] == "list": menu_permission_list.append((item["permissions__url"], item["permissions__title"])) print("menu_permission_list==>", menu_permission_list) request.session["menu_permission_list"] = menu_permission_list
获取"permissions__url", "permissions__action", "permissions__title"三个数据
permissions__action用于判断是不是list
若是是的话将对应的permissions__url和permissions__title存入session
其中permissions__url用于构建a标签中的href,permissions__title用于构建a标签中的文本
menu_permission_list注册的内容以下:
[ ('/stark/crm/customer/', '客户管理组'), ('/stark/crm/customer/public/', '客户管理组'), ('/stark/crm/customer/mycustomer/', '客户管理组') ]
模板代码
<div> {% for item in menu_permission_list %} <p class="menu_btn"><a href="{{ item.0 }}">{{ item.1 }}</a></p> {% endfor %} </div>
为了方便使用rbac组件,同时有利于解耦,能够在rbac目录下新建一个templates目录,将menu.html等相关模板放入到这个templates目录下,这样就把每一个app本身的模板都放到了同一个app目录下。
找的到吗?
在view中return一个模板一般会到主目录的templates中取寻找相应模板,但若是没找到则会到每一个app目录下的名为templates的目录中去寻找这个html模板
多个app的templates有同名模板怎么办?
会按照app注册优先顺序查找,先找到哪一个就返回哪一个,其它app下同名文件也相同
为了不这个问题,能够在app的templates目录下再新建一个目录,将模板放入这个目录,例如rbac\templates\rbac\menu.html
在视图中return一个模板时就要写“rbac/users.html”
提示:侧边栏可使用inclusion_tag来实现