模型: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來實現