rbac基于用户角色的权限管理

简介: rbac - 基于角色的权限管理,介绍了acl(访问权限列表)基于用户的权限管理,rbac基于角色的权限管理。

@TOC


前言

rbac - 基于角色的权限管理,介绍了acl(访问权限列表)基于用户的权限管理,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:资源表、角色表、用户表、角色资源表、接口权限表
```python
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.核心逻辑

```python
# 获取角色对应信息
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为当前用户所配置的资源模块
在这里插入图片描述
展示用户所具有的资源模块
在这里插入图片描述

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

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 资源互斥
    ~~~bash
    考试系统、比赛系统
    1 考试
    2 打分

互斥表
res1 res2
1 2
~~~

  • rbac3 继承+互斥
  • 位运算优化权限系统
    • 添加权限 | 对比权限& 删除权限^
相关文章
|
设计模式 算法 安全
【设计模式】RBAC 模型详解
随着软件系统的复杂性和规模的不断增长,权限管理成为了一个至关重要的问题。在大型多人协作的系统中,如何有效地管理不同用户的访问权限,确保系统的安全性和稳定性,是每一个开发者都需要面对的挑战。为了解决这一问题,业界提出了一种被广泛应用的权限管理模型——基于角色的访问控制(Role-Based Access Control,简称RBAC)。希望通过本篇博客的学习,您能够深入了解RBAC模型的核心思想和实现原理,掌握如何在实际项目中应用RBAC模型来提高系统的安全性和可维护性。
3279 1
|
2月前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
41629 72
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
6月前
|
数据安全/隐私保护
RBAC权限模型
RBAC(基于角色的访问控制)通过角色管理权限,实现用户与权限的间接关联,提升系统安全性与管理效率。其三大原则:最小权限、职责分离、数据抽象,使权限分配更清晰、灵活,广泛应用于现代权限管理系统中。
|
2月前
|
存储 安全 前端开发
【微服务】微服务安全:OAuth2.0、JWT、SSO单点登录、RBAC权限模型
本文系统梳理微服务安全四大核心:OAuth2.0(授权协议)、JWT(无状态凭证)、SSO(统一认证)、RBAC(权限模型),从边界定位、原理剖析、落地规范到协同架构四维展开,厘清分层职责与互补关系,提供企业级可落地的安全闭环实践指南。
|
6月前
|
安全 数据安全/隐私保护
RBAC权限模型
RBAC(基于角色的访问控制)通过角色管理权限,实现用户、角色、权限与资源的分离。其核心原则包括最小权限、职责分离与数据抽象,分为RBAC0至RBAC3四个层级,逐步支持角色继承与动态静态职责分离,提升系统安全与管理效率。