溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

使用Django框架怎么實(shí)現(xiàn)一個(gè)rbac權(quán)限組件

發(fā)布時(shí)間:2021-04-13 16:29:32 來源:億速云 閱讀:155 作者:Leah 欄目:開發(fā)技術(shù)

使用Django框架怎么實(shí)現(xiàn)一個(gè)rbac權(quán)限組件?很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

1.基于rbac的權(quán)限管理

RBAC(Role-Based Access Control,基于角色的訪問控制),就是用戶通過角色與權(quán)限進(jìn)行關(guān)聯(lián)。簡(jiǎn)單地說,一個(gè)用戶擁有若干角色,一個(gè)角色擁有若干權(quán)限。這樣,就構(gòu)造成“用戶-角色-權(quán)限”的授權(quán)模型。在這種模型中,用戶與角色之間,角色與權(quán)限之間都是多對(duì)多的關(guān)系。

簡(jiǎn)單的模型圖示如下:

使用Django框架怎么實(shí)現(xiàn)一個(gè)rbac權(quán)限組件

2.Rbac組件的基本目錄結(jié)構(gòu):

使用Django框架怎么實(shí)現(xiàn)一個(gè)rbac權(quán)限組件

3.按照寫的流程,來講解rbac組件中的各個(gè)部分,以及功能,

3.1 models數(shù)據(jù)庫(kù)表設(shè)計(jì)(models.py)。

為了在前端頁(yè)面實(shí)現(xiàn)2方面的控制,還需要引入兩個(gè)表菜單menu和分組group:1.在一個(gè)頁(yè)面,當(dāng)前用戶的權(quán)限,例如是否顯示添加按鈕、編輯、刪除等按鈕;2.左側(cè)菜單欄的創(chuàng)建。所以一共是5個(gè)類,7張表,詳細(xì)model請(qǐng)看下邊代碼。

models.py

# models.py

from django.db import models


class Menu(models.Model):
  '''頁(yè)面中的菜單名'''
  title = models.CharField(max_length=32)

class Group(models.Model):
  '''權(quán)限url所屬的組'''
  caption = models.CharField(verbose_name='組名稱',max_length=32)
  menu =models.ForeignKey(verbose_name='組所屬菜單',to='Menu',default=1) # 組所在的菜單

  class Meta:
    verbose_name_plural = 'Group組表'

  def __str__(self):
    return self.caption

class User(models.Model):
  """
  用戶表
  """
  username = models.CharField(verbose_name='用戶名',max_length=32)
  password = models.CharField(verbose_name='密碼',max_length=64)
  email = models.CharField(verbose_name='郵箱',max_length=32)

  roles = models.ManyToManyField(verbose_name='具有的所有角色',to="Role",blank=True)
  class Meta:
    verbose_name_plural = "用戶表"

  def __str__(self):
    return self.username

class Role(models.Model):
  """
  角色表
  """
  title = models.CharField(max_length=32)
  permissions = models.ManyToManyField(verbose_name='具有的所有權(quán)限',to='Permission',blank=True)
  class Meta:
    verbose_name_plural = "角色表"

  def __str__(self):
    return self.title


class Permission(models.Model):
  """
  權(quán)限表
  """
  title = models.CharField(verbose_name='標(biāo)題',max_length=32)
  url = models.CharField(verbose_name="含正則URL",max_length=64)
  is_menu = models.BooleanField(verbose_name="是否是菜單")

  code = models.CharField(verbose_name='url代碼',max_length=32,default=0) # 路徑對(duì)應(yīng)的描述名稱
  group = models.ForeignKey(verbose_name='所屬組',to='Group',null=True,blank=True)  # 所屬組

  class Meta:
    verbose_name_plural = "權(quán)限表"

  def __str__(self):
    return self.titlemodel

3.2 service中的init_permission.py

功能:在用戶登錄成功的時(shí)候,在session中寫入兩個(gè)內(nèi)容:1.拿到當(dāng)前用戶的權(quán)限url(code信息);2.拿到當(dāng)前用戶的可以做菜單的url信息。

詳細(xì)代碼如下:

初始化權(quán)限

def init_permission(user, request):
  '''
  前端頁(yè)面調(diào)用,把當(dāng)前登錄用戶的權(quán)限放到session中,request參數(shù)指前端傳入的當(dāng)前當(dāng)前l(fā)ogin請(qǐng)求時(shí)的request
  :param user: 當(dāng)前登錄用戶
  :param request: 當(dāng)前請(qǐng)求
  :return: None
  '''
  # 拿到當(dāng)前用戶的權(quán)限信息
  permission_url_list = user.roles.values('permissions__group_id',
                      'permissions__code',
                      'permissions__url',
                      'permissions__group__menu__id',   # 菜單需要
                      'permissions__group__menu__title',  # 菜單需要
                      'permissions__title',  # 菜單需要
                      'permissions__url',   # 菜單需要
                      'permissions__is_menu', # 菜單需要
                      ).distinct()


  # 頁(yè)面顯示權(quán)限相關(guān),用到了權(quán)限的分組,
  dest_dic = {}
  for each in permission_url_list:
    if each['permissions__group_id'] in dest_dic:
      dest_dic[each['permissions__group_id']]['code'].append(each['permissions__code'])
      dest_dic[each['permissions__group_id']]['per_url'].append(each['permissions__url'])
    else:
      # 剛循環(huán),先創(chuàng)建需要的結(jié)構(gòu),并把第一次的值放進(jìn)去。
      dest_dic[each['permissions__group_id']] = {'code': [each['permissions__code'], ],
                            'per_url': [each['permissions__url'], ]}

  request.session['permission_url_list'] = dest_dic

  # 頁(yè)面菜單相關(guān)
  # 1.去掉不做菜單的url,拿到的結(jié)果是menu_list,列表中的元素是字典
  menu_list = []
  for item_dic in permission_url_list:
    if item_dic['permissions__is_menu']:
      temp = {'menu_id':item_dic['permissions__group__menu__id'],
          'menu_title':item_dic['permissions__group__menu__title'],
          'permission__title': item_dic['permissions__title'],
          'permission_url':item_dic['permissions__url'],
          'permissions__is_menu':item_dic['permissions__is_menu'],
          'active':False,  # 用于頁(yè)面是否被選中,
          }
      # temp 其實(shí)只是給key重新起名字,之前的名字太長(zhǎng)了。。。。
      menu_list.append(temp)
  # 執(zhí)行完成之后是如下的數(shù)據(jù),用來做菜單。

  request.session['permission_menu_list'] = menu_list

3.3 中間件md

功能:1.白名單驗(yàn)證;

2.驗(yàn)證是否已經(jīng)寫入session,即:是否已經(jīng)登錄;

3.當(dāng)前訪問的url與當(dāng)前用戶的權(quán)限url進(jìn)行匹配驗(yàn)證,并在request中寫入code信息,

詳細(xì)代碼如下:

中間件

import re
from django.shortcuts import render,redirect,HttpResponse
from django.conf import settings

class MiddlewareMixin(object):
  def __init__(self, get_response=None):
    self.get_response = get_response
    super(MiddlewareMixin, self).__init__()

  def __call__(self, request):
    response = None
    if hasattr(self, 'process_request'):
      response = self.process_request(request)
    if not response:
      response = self.get_response(request)
    if hasattr(self, 'process_response'):
      response = self.process_response(request, response)
    return response

class M1(MiddlewareMixin):
  '''
  判斷用戶有無此url的權(quán)限的中間件
  '''
  def process_request(self,request):
    current_url = request.path_info

    # 1.白名單驗(yàn)證
    valid_url = settings.VALID_URL
    for each in valid_url:
      if re.match(each, current_url):
        return None

    # 2.驗(yàn)證是否已經(jīng)寫入session,即:是否已經(jīng)登錄
    permission_dic = request.session.get('permission_url_list')
    if not permission_dic:
      return redirect('/login/')

    # 3.與當(dāng)前訪問的url與權(quán)限url進(jìn)行匹配驗(yàn)證,并在request中寫入code信息,
    flag = False
    for group_id,code_urls in permission_dic.items():
      for url in code_urls['per_url']:
        regax = '^{0}$'.format(url)
        if re.match(regax,current_url):
          flag = True
          request.permission_code_list = code_urls['code'] # 在session中增加code的信息,用于在頁(yè)面判斷在當(dāng)前頁(yè)面的權(quán)限,
          break
      if flag:
        break

    if not flag:
      return HttpResponse('無權(quán)訪問')


  def process_response(self,request,response):
    return response

3.4 左側(cè)菜單的生成templatetags目錄下的rbac.py

功能;生成頁(yè)面中的左側(cè)菜單用inclusion_tag標(biāo)簽

運(yùn)用:我們只需要在需要用到的文件中引用就可以生成這個(gè)菜單部分的內(nèi)容。

需要用到的模板文件中:

{% load rbac %}

{% menu_html request %} 這部分就會(huì)變成用inclusion_tag生成的menu_html

詳細(xì)代碼如下:

inclusion_tag生成左側(cè)菜單

import re

from django.template import Library

register = Library()

# inclusion_tag的結(jié)果是:把menu_html函數(shù)的返回值,放到menu_html中做渲染,生成一個(gè)渲染之后的大字符串,
# 在前端需要顯示這個(gè)字符串的地方,只要調(diào)用menu_html就可以,如果有菜單需要傳參數(shù),這里是request,前端模板本來就有request,
@register.inclusion_tag('menu.html')
def menu_html(request):
  current_url = request.path_info

  # 結(jié)構(gòu)化在頁(yè)面顯示的menu數(shù)據(jù)
  menu_list = request.session.get('permission_menu_list')

  menu_show_dic = {}
  for item in menu_list:
    # 先跟當(dāng)前url進(jìn)行匹配,如果當(dāng)前的url在權(quán)限URl中,則需要修改當(dāng)前的active,用于在前端頁(yè)面的顯示。
    url = item['permission_url']
    reg = '^{0}$'.format(url)
    if re.match(reg, current_url):
      print('匹配到了')
      item['active'] = True

    if item['menu_id'] in menu_show_dic:
      menu_show_dic[item['menu_id']]['children'].append(
        {'permission__title': item['permission__title'], 'permission_url': item['permission_url'],
         'active': item['active']})
      if item['active']:
        menu_show_dic[item['menu_id']]['active'] = True
    else:
      menu_show_dic[item['menu_id']] = {'menu_id': item['menu_id'],
                       'menu_title': item['menu_title'],
                       'active': False,
                       'children': [{'permission__title': item['permission__title'],
                              'permission_url': item['permission_url'],
                              'active': item['active']}, ]
                       }
      if item['active']:
        menu_show_dic[item['menu_id']]['active'] = True


  return {'menu_dic':menu_show_dic}

需要的模板文件templates下的menu.html

menu.html

# menu.html

<div class="menu">
  {% for k,menu in menu_dic.items %}
    {# 一級(jí)菜單 #}
    <div class="menu_first">{{ menu.menu_title }}</div>

    {# 二級(jí)菜單(就是一級(jí)菜單下邊的內(nèi)容) #}
    {% if menu.active %}
      <ul class="">
    {% else %}
      <ul class="hide">
    {% endif %}

  {% for child in menu.children %}
    {% if child.active %}
      <li class="menu_second active"><a href="{{ child.permission_url }}" rel="external nofollow" rel="external nofollow" >{{ child.permission__title }}</a></li>
    {% else %}
      <li class="menu_second"><a href="{{ child.permission_url }}" rel="external nofollow" rel="external nofollow" >{{ child.permission__title }}</a></li>
    {% endif %}
  {% endfor %}
  </ul>
  {% endfor %}
</div>

使用inclusion_tag的文件示例:

inclusion_tag的使用模板文件

# 這個(gè)是django的模板文件
{% load rbac %}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{% block title %}模板{% endblock %}</title>
  <link rel="stylesheet" href="{% static 'rbac/bootstrap-3.3.7/css/bootstrap.min.css' %}" rel="external nofollow" >
  <link rel="stylesheet" href="{% static 'rbac/menu.css' %}" rel="external nofollow" >
  {% block css %} {% endblock css %}

</head>
<body>
<div class="container-fluid">
  <div class="row">
    <div class="col-md-2 menu">
      {% block menu %}
        {% menu_html request %}  {# 用inclusion_tag生成的menu_html #}
      {% endblock menu %}
    </div>
    <div class="col-md-9">
      {% block content %}
      content
      {% endblock %}
    </div>
  </div>
</div>

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI