温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

使用Django框架怎么实现一个rbac权限组件

发布时间:2021-04-13 16:29:32 来源:亿速云 阅读:161 作者:Leah 栏目:开发技术

使用Django框架怎么实现一个rbac权限组件?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

1.基于rbac的权限管理

RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间都是多对多的关系。

简单的模型图示如下:

使用Django框架怎么实现一个rbac权限组件

2.Rbac组件的基本目录结构:

使用Django框架怎么实现一个rbac权限组件

3.按照写的流程,来讲解rbac组件中的各个部分,以及功能,

3.1 models数据库表设计(models.py)。

为了在前端页面实现2方面的控制,还需要引入两个表菜单menu和分组group:1.在一个页面,当前用户的权限,例如是否显示添加按钮、编辑、删除等按钮;2.左侧菜单栏的创建。所以一共是5个类,7张表,详细model请看下边代码。

models.py

# models.py

from django.db import models


class Menu(models.Model):
  '''页面中的菜单名'''
  title = models.CharField(max_length=32)

class Group(models.Model):
  '''权限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='具有的所有权限',to='Permission',blank=True)
  class Meta:
    verbose_name_plural = "角色表"

  def __str__(self):
    return self.title


class Permission(models.Model):
  """
  权限表
  """
  title = models.CharField(verbose_name='标题',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) # 路径对应的描述名称
  group = models.ForeignKey(verbose_name='所属组',to='Group',null=True,blank=True)  # 所属组

  class Meta:
    verbose_name_plural = "权限表"

  def __str__(self):
    return self.titlemodel

3.2 service中的init_permission.py

功能:在用户登录成功的时候,在session中写入两个内容:1.拿到当前用户的权限url(code信息);2.拿到当前用户的可以做菜单的url信息。

详细代码如下:

初始化权限

def init_permission(user, request):
  '''
  前端页面调用,把当前登录用户的权限放到session中,request参数指前端传入的当前当前login请求时的request
  :param user: 当前登录用户
  :param request: 当前请求
  :return: None
  '''
  # 拿到当前用户的权限信息
  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()


  # 页面显示权限相关,用到了权限的分组,
  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:
      # 刚循环,先创建需要的结构,并把第一次的值放进去。
      dest_dic[each['permissions__group_id']] = {'code': [each['permissions__code'], ],
                            'per_url': [each['permissions__url'], ]}

  request.session['permission_url_list'] = dest_dic

  # 页面菜单相关
  # 1.去掉不做菜单的url,拿到的结果是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,  # 用于页面是否被选中,
          }
      # temp 其实只是给key重新起名字,之前的名字太长了。。。。
      menu_list.append(temp)
  # 执行完成之后是如下的数据,用来做菜单。

  request.session['permission_menu_list'] = menu_list

3.3 中间件md

功能:1.白名单验证;

2.验证是否已经写入session,即:是否已经登录;

3.当前访问的url与当前用户的权限url进行匹配验证,并在request中写入code信息,

详细代码如下:

中间件

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的权限的中间件
  '''
  def process_request(self,request):
    current_url = request.path_info

    # 1.白名单验证
    valid_url = settings.VALID_URL
    for each in valid_url:
      if re.match(each, current_url):
        return None

    # 2.验证是否已经写入session,即:是否已经登录
    permission_dic = request.session.get('permission_url_list')
    if not permission_dic:
      return redirect('/login/')

    # 3.与当前访问的url与权限url进行匹配验证,并在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的信息,用于在页面判断在当前页面的权限,
          break
      if flag:
        break

    if not flag:
      return HttpResponse('无权访问')


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

3.4 左侧菜单的生成templatetags目录下的rbac.py

功能;生成页面中的左侧菜单用inclusion_tag标签

运用:我们只需要在需要用到的文件中引用就可以生成这个菜单部分的内容。

需要用到的模板文件中:

{% load rbac %}

{% menu_html request %} 这部分就会变成用inclusion_tag生成的menu_html

详细代码如下:

inclusion_tag生成左侧菜单

import re

from django.template import Library

register = Library()

# inclusion_tag的结果是:把menu_html函数的返回值,放到menu_html中做渲染,生成一个渲染之后的大字符串,
# 在前端需要显示这个字符串的地方,只要调用menu_html就可以,如果有菜单需要传参数,这里是request,前端模板本来就有request,
@register.inclusion_tag('menu.html')
def menu_html(request):
  current_url = request.path_info

  # 结构化在页面显示的menu数据
  menu_list = request.session.get('permission_menu_list')

  menu_show_dic = {}
  for item in menu_list:
    # 先跟当前url进行匹配,如果当前的url在权限URl中,则需要修改当前的active,用于在前端页面的显示。
    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 %}
    {# 一级菜单 #}
    <div class="menu_first">{{ menu.menu_title }}</div>

    {# 二级菜单(就是一级菜单下边的内容) #}
    {% 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的使用模板文件

# 这个是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>

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI