文章目录
- 前言
- 一、rbac 基于角色的权限管理
- 1.acl 基于用户的权限管理
- 2.rbac 基于角色的权限管理
- 二、应用示例
- 1.配置角色资源
- a.分析表
- b.核心逻辑
- c.使用transfer在前端实现资源配置
- d.页面效果
- 2.登录时获取对应权限
- a.员工登录
- b.中间件
- c.前端请求
- d.效果图
- 3.前端-路由守卫-页面权限
- 三、拓展
前言
随着软件系统的日益复杂,权限管理成为确保系统安全与数据隐私的关键。传统ACL模型虽基础但受限,而RBAC模型以其灵活性和高效性逐渐成为主流。本文将简要回顾ACL概念,并深入解析RBAC的核心机制,包括角色定义、权限分配及用户角色关联。同时,将详细阐述RBAC在实际项目中的操作实践,如配置角色资源、动态获取用户权限及前端页面权限控制等。
一、rbac 基于角色的权限管理
1.acl 基于用户的权限管理
ACL(Access Control List,访问控制列表) 基于用户的权限管理
用户表:id、name、mobile、password1 张三 18612340000 1232 李四 18631240001 1329
资源表:id、name、pid1 内容菜单 null2 用户菜单 null3 课程管理 14 课程分类 15 用户管理 2
用户资源表:userid、resid1 31 42 5
2.rbac 基于角色的权限管理
rbac基于角色的权限管理
用户表:id、name、mobile、password、roleid1 张三 18612340000 1232 李四 18631240001 1329
角色表:id、name1 编辑人员 2 推广员3 财务专员
资源表:id、name、pid、url1 内容菜单 null2 用户菜单 null3 课程管理 1 /course/4 课程分类 1 /cates/5 用户管理 2 /user/
角色资源表:userid、resid1 31 42 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.nameclass 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.nameclass 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.usernameclass 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.urlclass 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 -- labelallres = [{"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)#添加传进来的 资源 idsreturn 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.dataconsole.log(res.data.data);})
})const setres=(rid)=>{roleid.value = ridhttp.get('resource/?roleid='+rid).then(res=>{data.value = res.data.allresvalue.value = res.data.checkedidsdialogVisible.value=trueconsole.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.idif pid not in idlist:rlist.append({"id":pid,"name":i.pid.name,'son':[]})idlist.append(pid)# 遍历rlistfor index,i in enumerate(rlist):#遍历resfor j in reso:# 找到父类的sonif j.pid.id == i['id']:rlist[index]['son'].append({"id":j.id,'name':j.name,'url':j.url})print("rlist--->")print(rlist)# 查询资源对应的接口权限列表,存入redisr.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/']# 获取当前的urlpath = request.pathprint("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已经过期不能操作"})#是否退出,退出时存tokenvalue = 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))// 用户 tokenlocalStorage.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 res21 2
- rbac3 继承+互斥
- 位运算优化权限系统
- 添加权限 | 对比权限& 删除权限^