1. 程式人生 > >Django 基於角色的許可權控制

Django 基於角色的許可權控制

有一種場景, 要求為使用者賦予一個角色, 基於角色(比如後管理員,總編, 編輯), 使用者擁有相應的許可權(比如管理員擁有所有許可權, 總編可以增刪改查, 編輯只能增改, 有些頁面的按鈕也只有某些角色才能檢視)

ENV: Python3.6 + django1.11

應用場景

有一種場景, 要求為使用者賦予一個角色, 基於角色(比如後管理員,總編, 編輯), 使用者擁有相應的許可權(比如管理員擁有所有許可權, 總編可以增刪改查, 編輯只能增改, 有些頁面的按鈕也只有某些角色才能檢視), 角色可以任意新增, 每個角色的許可權也可以任意設定

django 的許可權系統

django 預設的許可權是基於Model的add, change, delete來做許可權判斷的, 這種設計方式有一個明顯的缺陷, 比如怎麼控制對Model的某個欄位的修改的許可權的控制呢

設計許可權

大多數的系統, 都會給使用者賦予某個角色, 假如能針對使用者的角色, 做許可權控制,這個許可權控制並不侷限於對Model的修改, 可以是任意位置的許可權控制, 只要有一個許可權名, 即可根據使用者角色名下是否擁有許可權名判斷是否擁有許可權

User, Role => UserRole => RolePermissions

User 是使用者物件

Role: 角色表

UserRole 是使用者角色關係物件

RolePermissions 是角色許可權關係物件

因此, 需要建立三個ModelUser

,RoleUserRoleRolePermission

User 可以使用django 預設的User 物件

其他Model 如下

class Role(models.Model): """角色表""" # e.g add_user role_code = models.CharField('role code', max_length=64, unique=True, help_text = '使用者角色標識') # e.g 新增使用者 role_name = models.CharField('role name', max_length=64, help_text = '使用者角色名') class UserRole(models.Model): """使用者角色關係表""" user_id = models.IntegerField('user id', blank=False, help_text='使用者id', unique=True) role_codes = models.CharField('role codes', blank=True, default=None, max_length=256, help_text='使用者的角色codes') class RolePermission(models.Model): """角色許可權關係表""" role_code = models.CharField('role code', max_length=64, blank=False, help_text = '使用者角色標識') pm_code = models.CharField('permission code', blank=False, max_length=64, help_text='許可權code') class Meta: unique_together = ('role_code', 'pms_code') 

其中 Role 和 RolePermission 用於管理角色和對應的許可權的關係

UserRole 用於管理使用者和角色的對映關係

許可權管理

使用者角色擁有哪些許可權是在程式碼裡定義好的, 比如:

PMS_MAP = (
    ('PM_ADD_USER', '新增使用者'), ('PM_SET_MAIL', '編輯郵箱'), ... ) 

PM_ADD_USER 是許可權code碼, 新增使用者 是許可權名, 在這裡, 許可權名由我們定義, 後面在需要使用的地方做has_perm(<pm_coede>) 判斷時, 用的就是這是這個code

角色管理

在定義好許可權後, 我們就可以做角色管理了,

在這裡, 我們可以建立任意的角色, 為其分配任意的許可權, 當然, 最好建立有意義的角色

角色表單定義(forms.py)

role_regex_validator = RegexValidator(r"[a-zA-Z0-9]", "角色標記只能包含字母,數字, 下劃線") class RoleForm(forms.Form): role_row_code = forms.IntegerField(required=False, widget=forms.HiddenInput()) role_code = forms.CharField(label='角色標記', min_length=3, max_length=64, validators=[role_regex_validator]) role_name = forms.CharField(label='角色名', min_length=3, max_length=64) OPTIONS = PMS_MAP pms = forms.MultipleChoiceField(label='許可權列表', widget=forms.SelectMultiple(choices=OPTIONS) 

角色編輯views.py

def role_edit(request):
    """角色編輯""" if request.method == 'POST': role_row_id = request.POST.get('role_row_id', 0) role_code = request.POST.get('role_code', '') role_name = request.POST.get('role_name', '') pms = request.POST.getlist('pms', []) # 表單校驗 role_form = RoleForm({ 'role_row_id': role_row_id, 'role_code': role_code, 'role_name': role_name, 'pms': pms }) # 表單校驗 if not role_form.is_valid(): return render(request, 'role_form.html', {'form': role_form) role_row_id = role_form.cleaned_data.get('role_row_id', None) if role_row_id: # 角色更新 return update_role(request, role_form, role_row_id=role_row_id, role_code=role_code, role_name=role_name, pms=pms) else: # 角色建立 return add_role(request, role_form, role_code, role_name, pms=pms) else: # 角色編輯頁面 role_row_id = request.GET.get('id') try: role_item = Role.objects.get(pk=role_row_id) except Role.DoesNotExist as e: role_item = None if role_item: # 編輯已有角色表單 # 獲取角色許可權列表 role_pms_rows = RolePermission.objects.filter(role_code=role_item.role_code) pms_codes = [role_pms_row.pms_code for role_pms_row in role_pms_rows] role_form = RoleForm({ 'role_row_id': role_row_id, 'role_code': role_item.role_code, 'role_name': role_item.role_name, 'pms': pms_codes }) else: # 新增角色表單 role_form = RoleForm() return render(request, 'role_form.html', {'form': role_form}) def add_role(request, role_form, role_code, role_name, pms=()): """新增角色""" try: with transaction.atomic(): role_item = Role.objects.create(role_code=role_code, role_name=role_name) for pm_code in pms: RolePermission.objects.update_or_create(role_code=role_code, pms_code=pm_code) return redirect('{}?id={}'.format(reverse('user_role_edit'), role_item.pk)) except IntegrityError as e: # 創建出錯 role_form.add_error('role_code', '角色已經存在: {}'.format(role_code)) return render(request, 'role_form.html', {'form': role_form}) def update_role(request, role_form, role_row_id, role_code, role_name, pms=()): """更新角色""" try: with transaction.atomic(): role_item = Role.objects.get(pk=role_row_id) # 校驗合法性 if not role_item: raise Http404('非法的role記錄id') role_item.role_name = role_name role_item.save() # 刪除原角色許可權設定 RolePermission.objects.filter(role_code=role_code).delete() for pm_code in pms: RolePermission.objects.update_or_create(role_code=role_code, pms_code=pm_code) return redirect('{}?id={}'.format(reverse('user_role_edit'), role_row_id)) except IntegrityError as e: # 更新出錯 role_form.add_error('role_name', '更新角色名出錯:{}'.format(role_name)) return render(request, 'role_form.html', {'form': role_form}) 

表單部分html

<form class='form-horizontal' action='/user/role/edit' method='POST'> <p>使用者角色編輯</p> {{form.non_field_errors}} {% csrf_token %} {% for hidden_field in form.hidden_fields %} {{ hidden_field }} {% endfor %} {% for field in form.visible_fields %} <div class='form-group'> <label class='col-lg-2 control-label'>{{field.label}}</label> {% if field.errors %} <div class='col-lg-3 has-error'> {{field}} {% for error in field.errors %} <p><span class='help-block m-b-none'>{{error}}</span><p> {% endfor %} </div> {% else %} <div class='col-lg-3'> {{field}} </div> {% endif%} </div> {% endfor %} <div class="form-group"> <div class="col-lg-offset-2 col-lg-10"> <button class="btn btn-sm btn-white" type="submit">Save</button> </div> </div> </form> 

至此使用者角色編輯就完成了

還有一部分是使用者角色分配

說白了就是編輯使用者時, 給使用者選擇一個角色, 將使用者id和角色code 通過UserRole存到資料庫中, 這一部分請各位自己實現吧 :)

許可權判斷

如果我們有了一個使用者, 並賦予了一個角色, 應該怎麼判斷其是否有某個許可權呢

在django的認證體系配置裡, 有一項配置是AUTHENTICATION_BACKENDS, 比如, 我們希望對接sso 單點登入, 就可以在這裡新增配置

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'cas.backends.CASBackend', # 單點登入實現 ) 

這個配置項下每個字串都對應一個類, 每個類繼承並了一組介面實現中的全部或其中一部分

這組介面定義了使用者認證和授權的相關內容, 如下

authenticate
get_user_permissions
get_group_permissions
has_perm
...

其中 has_perm 就是直接判斷是否擁有某個許可權的介面

在 AUTHENTICATION_BACKENDS中, 只要有一個類的 has_perm 判定使用者擁有某個許可權即可認為使用者擁有該許可權

因此, 我們實現自己的has_perm 實現

class PermBackend(ModelBackend):
    def has_perm(self, user_obj, pms_code, obj): if not user_obj.is_active: return False # 超級管理員擁有所有許可權 if user_obj.is_superuser: return True try: user_roles_record = UserRole.objects.get(user_id=user_obj.pk) except UserRole.DoesNotExist as e: return False # 獲取使用者的角色(暫時是單個角色) user_roles = user_roles_record.role_codes.split(',') # 角色對應的許可權集合 role_pms_rows = RolePermission.objects.filter(role_code__in=user_roles) pms_codes = [role_pms_row.pms_code for role_pms_row in role_pms_rows] # pms_code 是否在使用者的許可權code集合中 return pms_code in pms_codes 

當然, 別忘了把PermBackend 放到 AUTHENTICATION_BACKENDS 最後

現在, 我們在views funcion的實現前新增 @permission_required(<pms_code>) 裝飾器就能根據當前使用者的角色, 判定是否擁有某個<pms_code> 許可權啦

無論是任何地方, 只要定義唯一的pms_code, 賦給角色, 並將角色分配給某個使用者, 就可以實現粒度很深的許可權控制, 在本文開始的地方的所說的對某個Model單欄位的修改許可權也就不在話下了