首页 > 其他分享 >Django Web开发:构建强大RBAC权限管理系统的实战指南

Django Web开发:构建强大RBAC权限管理系统的实战指南

时间:2024-07-28 17:54:05浏览次数:11  
标签:Web name models Django RBAC token 权限 data id

在这里插入图片描述


文章目录


前言

    随着软件系统的日益复杂,权限管理成为确保系统安全与数据隐私的关键。传统ACL模型虽基础但受限,而RBAC模型以其灵活性和高效性逐渐成为主流。本文将简要回顾ACL概念,并深入解析RBAC的核心机制,包括角色定义、权限分配及用户角色关联。同时,将详细阐述RBAC在实际项目中的操作实践,如配置角色资源、动态获取用户权限及前端页面权限控制等。


一、rbac 基于角色的权限管理

1.acl 基于用户的权限管理

ACL(Access Control List,访问控制列表) 基于用户的权限管理

用户表:id、name、mobile、password
		1 张三	18612340000	123
		2 李四	18631240001	1329
资源表:id、name、pid
	  	1 内容菜单 null
		2 用户菜单 null
		3 课程管理  1
		4 课程分类  1
		5 用户管理  2
用户资源表:userid、resid
			1		3
			1		4
			2		5

2.rbac 基于角色的权限管理

rbac基于角色的权限管理

用户表:id、name、mobile、password、roleid
		1 张三	18612340000	123
		2 李四	18631240001	1329
角色表:id、name
		1 编辑人员	
		2 推广员
		3 财务专员
资源表:id、name、pid、url
	  	1 内容菜单 null
		2 用户菜单 null
		3 课程管理  1  /course/
		4 课程分类  1  /cates/
		5 用户管理  2  /user/
角色资源表:userid、resid
			1		3
			1		4
			2		5

流程:
员工入职–>管理员—>添加角色、添加资源、角色配置资源、添加用户(选择角色)—>用户登录—>手机号验证码—>验证通过,通过后查询此用户对应的资源(页面资源、接口资源)—>把页面资源返回给前端—>把接口资源存入redis—>在中间件验证是否有接口权限

二、应用示例

1.配置角色资源

a.分析表

user/models.py:资源表、角色表、用户表、角色资源表、接口权限表

from django.db import models

# Create your models here.
# 资源表
class ResourceModel(models.Model):
    name = models.CharField(max_length=10, unique=True, default="默认模块")
    pid = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='son')
    url = models.CharField(max_length=20, unique=True)
    def __str__(self):
        return self.name
    class Meta:
        verbose_name = "资源表"
        verbose_name_plural = "资源表"
        db_table = 'resource'

# 角色表
class RoleModel(models.Model):
    name = models.CharField(max_length=10,unique=True ,default="角色")
    resource = models.ManyToManyField(ResourceModel)
    def __str__(self):
        return self.name
    class Meta:
        verbose_name = "角色表"
        verbose_name_plural = "角色表"
        db_table = 'role'

# 用户表
class UserModel(models.Model):
    username = models.CharField(max_length=10, default='默认用户名')
    phone = models.CharField(max_length=11, unique=True)
    password = models.CharField(max_length=10, default='123')
    role = models.ForeignKey(RoleModel, on_delete=models.CASCADE,default=2)
    def __str__(self):
        return self.username
    class Meta:
        verbose_name = "用户表"
        verbose_name_plural = "用户表"
        db_table = 'user'

# 接口权限表:
class InterfacePerModel(models.Model):
    resource = models.ManyToManyField(ResourceModel,related_name="interfaces")
    url = models.CharField(max_length=20, unique=True)
    def __str__(self):
        return self.url
    class Meta:
        verbose_name = '接口权限表'
        verbose_name_plural = '接口权限表'
        db_table = 'interface_per'

b.核心逻辑

# 获取角色对应信息
class RoleView(APIView):
    def get(self, request):
        data = RoleModel.objects.all()
        roleSer = RoleSerializer(data, many=True)
        return Response({"message":"获取角色资源对应信息","code":200,"data":roleSer.data})

class ResourceView(APIView):
    def get(self, request):
        # 拿 roleid
        # 拿 roleid 对应的资源,
        roleid = request.GET.get('roleid')
        res = ResourceModel.objects.filter(pid_id__gt=0).all()
        # transf -- key -- label
        allres = [{"key":i.id,"label":i.name} for i in res] #pid>0,所有资源

        role = RoleModel.objects.filter(id=roleid).first()
        checkres = role.resource.all() #拿这个角色对应的所有资源(选中)
        checkedids = [i.id for i in checkres] #拿对应的所有资源id(选中)
        return Response({"code":200,"allres":allres,"checkedids":checkedids})

    def post(self, request):
        resids = request.data.get("resids")
        roleid = request.data.get("roleid")
        role = RoleModel.objects.filter(id=roleid).first()
        print(role.resource.all())
        if role.resource.all():
            role.resource.clear()#清除之前存在的所有资源
        role.resource.add(*resids)#添加传进来的 资源 ids
        return Response({"code":200})

c.使用transfer在前端实现资源配置

使用 el-transfer 构建角色资源配置页面

<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="name" label="角色名" width="120" />
    <el-table-column fixed="right" label="Operations" width="120">
      <template #default="scope">
        <el-button link type="primary" size="small" @click="handleClick">Detail</el-button>
        <el-button link type="primary" size="small">Edit</el-button>
        <el-button link type="primary" size="small" @click="setres(scope.row.id)">资源配制</el-button>
      </template>
    </el-table-column>

  </el-table>
  <el-dialog v-model="dialogVisible" title="Tips" width="80%">
    
    <el-transfer v-model="value" :data="data" />

    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">Cancel</el-button>
        <el-button type="primary" @click="addresource">Confirm</el-button>
      </span>
    </template>
  </el-dialog>


</template>

<script setup>
import http from "../../http";
import {onMounted,ref} from 'vue'

const dialogVisible=ref(false)
const value=ref([])
const data = ref([])
const roleid = ref('')

const handleClick = () => {
  console.log('click')
}

const tableData = ref([])
onMounted(()=>{
    http.get('role/').then(res=>{
        tableData.value = res.data.data
        console.log(res.data.data);
    })
})

const setres=(rid)=>{
    roleid.value = rid
    http.get('resource/?roleid='+rid).then(res=>{
        data.value = res.data.allres
        value.value = res.data.checkedids
        dialogVisible.value=true
        console.log(res.data.allres);
        console.log(res.data.checkedids);
    })
    
}

const addresource=()=>{
    http.post('resource/',{'roleid':roleid.value,'resids':value.value}).then(res=>{
        roleid.value=''
        dialogVisible.value=false
    })
}
</script>

d.页面效果

在这里插入图片描述

2.登录时获取对应权限

a.员工登录

课程模块
		分类管理
		课程管理
接口权限表:资源id(manytomany)、接口地址
			2				/cates/
			3				/courses/
			3				/cates/

手机号—>验证通过后获取roleid,通过roleid获取资源列表,把资源列表返回给前端
eg:[{"id":1,"name":"课程模块","sons":[{"id":2,"name":"分类管理",'url':""}]}]
查询资源对应的接口权限列表,存入redis

# 账号密码登录
class LoginView(APIView):
    def post(self, request):
        # 获取参数
        # username = request.data.get('username', None)
        # password = request.data.get('password', None)
        phone = request.data.get('phone', None)
        # 验证码
        # ...

        # 查询user表
        print(phone)
        user = UserModel.objects.filter(phone__exact=phone).first()
        reso = user.role.resource.all()

        # 构建父类资源/资源信息重组、
        rlist = []
        idlist = []
        interlist=[] #接口权限列表
        for i in reso:
            # 获取此资源的所有接口
            interfaces = i.interfaces.all()
            for interface in interfaces:
                interlist.append(interface.url)

            pid = i.pid.id
            if pid not in idlist:
                rlist.append({"id":pid,"name":i.pid.name,'son':[]})
                idlist.append(pid)

        # 遍历rlist
        for index,i in enumerate(rlist):
            #遍历res
            for j in reso:
                # 找到父类的son
                if j.pid.id == i['id']:
                    rlist[index]['son'].append({"id":j.id,'name':j.name,'url':j.url})
        print("rlist--->")
        print(rlist)
        
        # 查询资源对应的接口权限列表,存入redis
        r.set_str('user'+str(user.id)+'interface',json.dumps(interlist))

        # if not user:
        #     return Response({"code":"2001",'message': 'User not found'})
        # if password != user.password:
        #     return Response({"code":"2002",'message': 'Wrong password'})
        token = mjwt.jwt_encode({"userid":user.id,'exp':int(time.time())+3600})
        return Response({"code":"200",'message': 'Successfully logged in',"token":token,"rlist":rlist})

b.中间件

中间件:
token 是否存在、是否过期、是否退出
从token中解析出userid,根据userid查询接口权限列表,查询redis
request.path not in reslist:

class PermitionMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 1.定义白名单,在登录前需要操作的接口放到白名单中
        wlist = ['/register/', '/login/', '/sendsms/']
        # 获取当前的url
        path = request.path
        print("middleware---------------------------1")
        print(path)
        # 2.如果不在白名单,获取token,验证
        if path not in wlist:
            print("middleware----------------------------2")
            try:
                token = request.headers.get('Authorization')
                data = mjwt.jwt_decode(token)
            except:
                return JsonResponse({"code": 401, "mes": "token不存在或者被修改"})
            # data没问题 ↓
            exp = int(data['exp'])
            now = int(time.time())
            userid = data['user_id']
            request.userid = userid
            # 判断是否过期
            if now > exp:
                return JsonResponse({"code": 401, "mes": "token已经过期不能操作"})
            #是否退出,退出时存token
            value = r.get_str(token)
            if value:
                return JsonResponse({"code": 401, "mes": "用户已经退出,不能操作"})
            # 3.↑↑↑验证是否被修改,是否过期,是否已经退出 (点击退出,把token存入redis, 加一个过期时间),任何一个问题,return 401没有权限操作,通过继续下一步操作
            #验证是否有权限操作接口
            list = r.get_str('user'+str(userid)+'interface')
            if list:
                list = json.loads(list)
                if path not in list:
                    return JsonResponse({"code":401,"mes":"没有操作此接口的权限"})

c.前端请求

登录请求

    fnLogin() {
        // alert("登录")
        http.post("http://localhost:8000/login/",{phone:this.user.phone,password:this.user.password}).then((result) => {
            console.log(result.data.rlist);
            if (result.data.code == '200') {
                alert(result.data.message)
                // 资源列表
                localStorage.setItem('rlist',JSON.stringify(result.data.rlist))
                // 用户 token
                localStorage.setItem('token',result.data.token)
                // localStorage.setItem("phone",this.user.phone)
                // localStorage.setItem('menulist',JSON.stringify(result.data.menulist))
                // this.$router.push('/index')
                this.$router.push('/home')
            } else {
                alert(result.data.message)
            }
        }).catch((err) => {
            alert(err)
        });
    }

d.效果图

登录成功后,跳转到home页面,展示当前用户所具有的功能模块
List2为当前用户所配置的资源模块
在这里插入图片描述
展示用户所具有的资源模块
在这里插入图片描述

3.前端-路由守卫-页面权限

1.login接口返回menulist
2.vue页面调用登录接口
3.路由守卫

// 导航守卫
router.beforeEach((to, from, next) => {
  var reslist = ['/login', '/register', '/home', '/', '/chat']

  if (reslist.indexOf(to.path) == -1) {
    var token = localStorage.getItem('token')
    if (token) {
      //验证是否在权限列表中
      // var menulist = localStorage.getItem('menulist');
      var menulist = localStorage.getItem('rlist');
      // var mlist = JSON.parse(menulist)
      var mlist: any[] = menulist ? JSON.parse(menulist) : [];
      console.log("milist-------");
      console.log(mlist);
      if (mlist.indexOf(to.path) >= 0) {
        next()
      } else {
        alert("无权访问此页面")
        next({ "name": '/' })
      }
    } else {
      next({ "name": 'Login' })
    }
  }
  //对于登录、注册、首页等不需要权限的页面,直接放行
  next()

})

三、拓展

  • rbac0 基础
    • 四张表
  • rbac1 角色继承
    • 基础角色–>配置资源
    • 角色->继承基础角色
    • 角色表:id、name、pid
  • rbac2 资源互斥
考试系统、比赛系统
	1	  考试
	2	  打分

互斥表
res1	res2
 1		  2
  • rbac3 继承+互斥
  • 位运算优化权限系统
    • 添加权限 | 对比权限& 删除权限^

在这里插入图片描述

标签:Web,name,models,Django,RBAC,token,权限,data,id
From: https://blog.csdn.net/m0_48173416/article/details/140502951

相关文章

  • django学习入门系列之第五点《javascript的条件语句和函数》
    文章目录5.6条件语句5.7函数往期回顾5.6条件语句if(){}elseif(){}5.7函数#python中函数定义的格式deffunc{函数的内容}#使用函数func()//javascript函数中的内容functionfunc(){函数的内容}//使用函数func()往......
  • Caddy web服务器
    caddy中文文档:https://caddy2.dengxiaolong.com/docs/常用命令命令描述caddyrun启动Caddy服务器caddyreload重载Caddy配置caddystart启动Caddy服务器,在后台运行caddystop优雅地停止Caddy服务器caddyinstall安装软件包caddyuninstall卸载软......
  • 如何在 Django 中调试失败的测试?
    如何调试我的测试?例如,我通过POST创建一个条目并期望它验证并返回特定页面。它可以在浏览器和shell中工作,但测试是唯一失败的事情(讽刺的是!)。我想打印对控制台的响应或其他东西,这样我就可以读取错误或你有什么。但我只能看到我print中的东西,例如视图。不确定是否......
  • web期末作业设计网页/web前端开发期末大作业/html css网页制作成品(第51-60套/总计100
     博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩。项目配有对应开发文档、开题报告、任务书、P......
  • Django提示mysql版本过低:django.db.utils.NotSupportedError: MySQL 8 or later is re
    这个提示只是Django的版本检测提示,把它注释掉就好了。全局搜索函数:check_database_version_supported() 文件路径是:django_project\dj01\venv\Lib\site-packages\django\db\backends\base\base.py找到第二个,就是就是使用的那个,把它注释了: definit_connection_sta......
  • 使用 DigitalOcean Spaces 在 Django 应用程序中初始化 boto3 会话时出错
    当我尝试将Django应用程序配置为使用DigitalOceanSpaces处理静态文件和媒体文件时,我遇到了问题。这是我的settings.py文件的相关部分:importboto3frombotocore.exceptionsimportNoCredentialsError,PartialCredentialsErrorfrombotocore.clientimportCo......
  • 识别web上的资源请求
    识别Web上的资源HTTP请求的目标称为“资源”,其性质没有进一步定义;它可以是文档、照片或其他任何东西。每个资源都由统一资源标识符(URI)标识,该标识符在整个HTTP中用于标识资源。URL和URN网址最常见的URI形式是统一资源定位符(URL),也称为Web地址。网址复......
  • 在 Google Colab 上运行 Django:错误 403 Forbidden
    我正在尝试对我的Python程序的Colab进行一些测试并使用Django。我按照此链接中的说明进行操作。我确保在settings.py中设置了此设置ALLOWED_HOSTS=['*']运行此命令以获取链接https://randomstrings.colab.googleusercontent.com/fromgo......
  • Django 测试设置错误:MySQL 后端的 django_content_type 表问题
    我在使用MySQL后端设置Django测试时遇到问题。该错误发生在测试数据库设置阶段,特别与django_content_type表相关。详细信息如下:环境:Django版本:5.0.7MySQL版本:8.0.37操作系统:Ubuntu20.04Python版本:3.11.12......
  • Django Haystack 多值字符串分面——未知字段tags_exact
    我网站上的内容用可变长度的字符串标记,我想对这些标记进行分面搜索。例如,一个故事可能有标签“内战”、“格兰特将军”和“葛底斯堡之战”。我希望能够对精确的非标记化字符串进行分面搜索。在我的search_index.py中,我定义了:tags=MultiValueField(faceted=True,in......