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"])
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章