1. 程式人生 > >rbac許可權驗證

rbac許可權驗證

模型:rbac 基於角色的許可權訪問控制
什麼是許可權?
一個包含正則表示式的url就是許可權

第一個版本,實現簡單的許可權驗證需求

表結構:

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

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),

Login(註冊session):

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.py

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("您沒有許可權訪問!")

在使用正則驗證許可權的時候要注意在確定校驗規則時別忘了在路徑前後加上^和$

校驗包含三個部分:
白名單校驗(正則)
登入校驗
許可權校驗

前端頁面的校驗

前端頁面驗證當前登入使用者是否有登入許可權,如果有就顯示相應選項

{% 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>

思考:上面的操作已經實現許可權控制的需求,但是判斷許可權使用的條件是寫死的,例如/userinfo/add/,如果能讓程式碼更通用更簡潔?


變更資料結構:
新增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

url

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

將註冊session功能單獨提取出來,與rbac模組放到一起
perssions.py

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

中介軟體(不要忘了註冊到settings.py中)

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("沒有訪問許可權!")

檢視views

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 %}

附錄:註冊session時的資料轉換

在上面initial_session中permissions獲取到的資料格式為:

[
     {'permissions__url': '/users/add/', 
     'permissions__group_id': 1, 
     'permissions__action': 'add'}, 

     {'permissions__url': '/roles/', 
     'permissions__group_id': 2, 
     'permissions__action': 'list'}, 

     {'permissions__url': '/users/delete/(\\d+)', 
     'permissions__group_id': 1, 
     'permissions__action': 'delete'}, 

     {'permissions__url': 'users/edit/(\\d+)', 
     'permissions__group_id': 1, 
     'permissions__action': 'edit'}
]

我們需要把它轉換為我們想要的格式:

permission_dict={        
 1: {
 'urls': ['/users/', '/users/add/', '/users/delete/(\\d+)', 'users/edit/(\\d+)'], 
 'actions': ['list', 'add', 'delete', 'edit']}, 

 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"])