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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 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 继承+互斥
  • 位运算优化权限系统
    • 添加权限 | 对比权限& 删除权限^
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
Kubernetes 容器
如何为不同的用户组配置不同的 RBAC 权限?
如何为不同的用户组配置不同的 RBAC 权限?
|
7月前
|
数据安全/隐私保护
基于RBAC0模型的简单权限系统设计角色
基于RBAC0模型的简单权限系统设计角色
|
Kubernetes 安全 中间件
RBAC权限管理(一)
RBAC权限管理
1206 0
|
监控 安全 数据安全/隐私保护
|
SQL 安全 关系型数据库
第03章 用户与权限管理
第03章 用户与权限管理
112 0
|
数据安全/隐私保护
11-企业权限管理-角色操作
11-企业权限管理-角色操作
11-企业权限管理-角色操作
|
数据安全/隐私保护
RBAC基于角色的访问控制权限的基本模型
RBAC基于角色的访问控制权限的基本模型
173 0
RBAC基于角色的访问控制权限的基本模型
|
安全 jenkins 持续交付
Jenkins 用户角色权限管理
Jenkins 一般用作团队项目持续集成环境,所以就会设计多用户的情况,我们需要为不同人员设置不同的角色,进行权限管理。
Jenkins 用户角色权限管理
|
安全 Java 数据管理
基于角色访问控制RBAC权限模型的动态资源访问权限管理实现
前面主要介绍了元数据管理和业务数据的处理,通常一个系统都会有多个用户,不同用户具有不同的权限,本文主要介绍基于RBAC动态权限管理在crudapi中的实现。RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。模型中有几个关键的术语: 用户:系统接口及访问的操作者 权限:能够访问某接口或者做某操作的授权资格 角色:具有一类相同操作权限的用户的总称 。 #### 用户角色权限关系 一个用户有一个或多个角色 一个角色包含多个用户 一个角色有多种权限 一个权限属于多个角色
730 0
基于角色访问控制RBAC权限模型的动态资源访问权限管理实现
|
数据安全/隐私保护
RBAC角色权限设计
RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。
4131 0