DncZeus实战开源项目(二)Rbac权限设计
DncZeus 项目 RBAC 权限设计详细解析
DncZeus 作为基于 .NET 7 + Vue.js 的前后端分离通用后台管理系统框架,其权限设计核心围绕 RBAC(Role-Based Access Control,基于角色的访问控制) 模型展开,同时结合 JWT 令牌认证机制,实现了 “用户 - 角色 - 权限” 的三层权限管控,覆盖页面访问、操作按钮、数据接口等全场景权限控制。以下从设计核心、权限层级、实现细节、流程链路等方面详细解析:
一、RBAC 核心设计理念
RBAC 模型的核心是 “通过角色关联用户与权限”,避免用户与权限直接绑定导致的权限管理混乱(如用户数量多时,权限变更需逐个操作)。DncZeus 严格遵循这一理念,核心设计目标:
-
解耦用户与权限:用户仅关联角色,权限仅关联角色,权限变更只需调整角色配置;
-
动态权限控制:支持运行时修改角色权限,无需重启系统即可生效;
-
全链路权限校验:前端渲染、后端接口、数据访问三层均实现权限拦截。
二、权限层级与核心实体
DncZeus 的 RBAC 模型包含 4 个核心实体,通过关联关系实现权限的灵活分配:
| 实体 | 作用说明 | 核心属性(示例) |
|---|---|---|
| 用户(User) | 权限的持有者,如 “administrator(超级管理员)”“admin(普通管理员)” | 用户名、密码、关联角色 ID 列表 |
| 角色(Role) | 权限的集合载体,用于关联用户与权限,如 “超级管理员”“运营角色”“只读角色” | 角色名称、角色描述、关联权限 ID 列表 |
| 权限(Permission) | 具体的操作或访问权限,分为 “菜单权限”“按钮权限”“接口权限” 三类 | 权限编码、权限名称、权限类型、关联资源路径 |
| 资源(Resource) | 权限对应的系统资源,如菜单页面、操作按钮、API 接口 | 资源路径(如 /system/user)、资源类型 |
实体关联关系
-
用户与角色:多对多(一个用户可拥有多个角色,一个角色可分配给多个用户);
-
角色与权限:多对多(一个角色可包含多个权限,一个权限可分配给多个角色);
-
权限与资源:一对一(一个权限绑定一个系统资源,如 “用户列表查看权限” 绑定 /system/user 菜单资源)。
三、权限类型细分(覆盖全场景)
DncZeus 的权限设计覆盖 “前端可视、后端可操作、接口可访问” 三个维度,具体分为三类:
1. 菜单权限(前端页面访问控制)
-
作用:控制用户是否能看到系统中的某个菜单,以及是否能跳转到对应页面;
-
实现逻辑:
-
- 权限表中存储菜单的路径(如 /system/role)、菜单名称、图标等信息;
-
- 角色关联菜单权限后,用户登录时会拉取 “当前用户关联角色的所有菜单权限”;
-
- 前端将菜单数据存储到 Vuex 中,通过路由守卫拦截未授权的页面跳转(如普通用户访问 /system/role 会被 redirect 到无权限页面)。
2. 按钮权限(前端操作控制)
-
作用:控制用户是否能看到页面中的某个操作按钮(如 “新增用户”“删除角色”);
-
实现逻辑:
-
- 权限表中存储按钮的唯一编码(如 user:add)、按钮名称;
-
- 前端通过自定义指令(如 v-permission="['user:add']")判断当前用户权限列表中是否包含该按钮编码;
-
- 若不包含,则隐藏按钮或禁用按钮,避免用户触发无效操作。
3. 接口权限(后端数据访问控制)
-
作用:防止用户通过直接调用 API 接口绕过前端权限限制(如普通用户直接调用 /api/user/delete 接口删除数据);
-
实现逻辑:
-
- 后端通过 JWT 令牌验证 + 权限拦截器 实现;
-
- 每个 API 接口绑定对应的权限编码(如 /api/user/delete 绑定 user:delete 权限);
-
- 用户请求接口时,携带 JWT 令牌,后端解析令牌获取用户信息,查询用户关联的权限列表;
-
- 若权限列表中不包含接口绑定的权限编码,则返回 403 禁止访问。
四、权限流转全流程(结合 JWT 认证)
DncZeus 的权限控制与 JWT 令牌认证深度绑定,从用户登录到系统操作的全链路权限校验流程如下:
1. 登录阶段:权限初始化
-
用户在前端输入用户名 / 密码(如 administrator/111111),发送 HTTP 请求到后端 /api/v1/account/login;
-
后端验证用户名密码正确性后,查询该用户关联的 角色列表,再通过角色列表查询对应的 权限列表(含菜单、按钮、接口权限);
-
后端生成 JWT 令牌(令牌中包含用户 ID、用户名、角色 ID 等核心信息,避免频繁查询数据库),并将权限列表缓存到 Redis(键为用户 ID,值为权限编码集合);
-
后端返回 JWT 令牌给前端,前端将令牌存储到 localStorage 中,用于后续所有请求的身份验证。
2. 菜单加载阶段:动态渲染菜单
-
登录成功后,前端发送 HTTP 请求 /api/v1/account/profile/menu,携带 JWT 令牌;
-
后端验证令牌有效性,通过令牌中的用户 ID 读取 Redis 缓存的权限列表,筛选出 “菜单类型” 的权限;
-
后端返回菜单数据(含菜单路径、层级、图标等),前端将数据存储到 Vuex;
-
前端路由系统根据菜单数据动态生成可访问路由,渲染左侧菜单导航栏。
3. 页面操作阶段:按钮权限控制
-
前端页面加载时,通过自定义指令 v-permission 遍历页面中的操作按钮;
-
指令从 Vuex 中获取当前用户的权限编码集合,判断是否包含按钮对应的权限编码;
-
若包含则显示按钮,若不包含则隐藏或禁用按钮(如普通用户无 role:edit 权限,则 “编辑角色” 按钮隐藏)。
4. 接口请求阶段:接口权限校验
-
前端发起 API 请求(如删除用户)时,自动在请求头中携带 JWT 令牌;
-
后端通过中间件拦截请求,首先验证 JWT 令牌的有效性(是否过期、签名是否正确);
-
令牌验证通过后,解析用户 ID,从 Redis 中获取该用户的权限编码集合;
-
后端获取当前请求接口绑定的权限编码(如 /api/user/delete 绑定 user:delete);
-
校验用户权限集合中是否包含该权限编码:
-
- 包含:允许访问,执行接口业务逻辑并返回数据;
-
- 不包含:返回 403 Forbidden 错误,拒绝执行操作。
5. 权限变更阶段:动态生效
-
超级管理员通过系统修改角色权限(如给 “运营角色” 添加 user:export 权限);
-
后端更新角色与权限的关联关系,并清空该角色下所有用户的 Redis 权限缓存;
-
用户下次发起请求时,后端会重新查询用户的权限列表并缓存到 Redis,新权限即时生效(无需用户重新登录)。
五、关键技术实现细节
1. 后端权限拦截核心
- 基于 ASP.NET Core 的 ActionFilterAttribute 实现权限拦截器:
[AttributeUsage(AttributeTargets.Method)]
public class PermissionAttribute : ActionFilterAttribute
{
private readonly string _permissionCode;
public PermissionAttribute(string permissionCode)
{
_permissionCode = permissionCode;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// 1. 从 JWT 令牌解析用户 ID
var userId = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "UserId")?.Value;
// 2. 从 Redis 获取用户权限集合
var userPermissions = RedisHelper.Get<List<string>>($"UserPermissions:{userId}");
// 3. 校验权限
if (!userPermissions.Contains(_permissionCode))
{
context.Result = new ForbidResult(); // 返回 403
}
base.OnActionExecuting(context);
}
}
- 接口使用示例(给 /api/user/delete 接口绑定 user:delete 权限):
[HttpDelete]
[Permission("user:delete")]
public IActionResult DeleteUser(int id)
{
// 业务逻辑
return Ok();
}
2. 前端权限指令实现
- 自定义 Vue 指令 v-permission,用于按钮权限控制:
Vue.directive('permission', {
inserted: function(el, binding) {
// 从 Vuex 获取用户权限集合
const userPermissions = store.state.user.permissions;
// 绑定的权限编码(如 ['user:add'])
const requiredPermissions = binding.value;
// 校验是否包含所需权限
const hasPermission = requiredPermissions.some(code => userPermissions.includes(code));
if (!hasPermission) {
el.style.display = 'none'; // 隐藏无权限按钮
}
}c
});
- 页面中使用示例:
<Button v-permission="['user:add']" type="primary">新增用户</Button>
3. 数据库设计支撑
核心数据表设计(简化版):
-
Users(用户表):Id、UserName、Password(加密存储)、IsActive;
-
Roles(角色表):Id、RoleName、Description、IsActive;
-
UserRoles(用户角色关联表):UserId、RoleId(联合主键);
-
Permissions(权限表):Id、PermissionCode(唯一)、PermissionName、ResourceType(菜单 / 按钮 / 接口)、ResourcePath;
-
RolePermissions(角色权限关联表):RoleId、PermissionId(联合主键)。
六、权限设计的优势
-
低耦合易扩展:用户、角色、权限三层分离,新增角色(如 “财务角色”)、新增权限(如 “财务报表导出”)时,无需修改核心代码,只需配置关联关系;
-
前后端双重校验:前端隐藏无权限资源避免用户误操作,后端拦截未授权接口避免非法访问,安全性更高;
-
动态生效:权限变更后无需重启系统,用户下次请求时自动加载最新权限;
-
适配多场景:支持不同角色的精细化权限控制(如超级管理员拥有所有权限,普通管理员仅拥有数据查看和编辑权限)。
七、使用建议
-
权限编码设计:建议采用 “资源:操作” 的格式(如 user:add、role:delete),便于维护和扩展;
-
缓存优化:用户权限列表缓存到 Redis 中,避免每次接口请求都查询数据库,提升性能;
-
超级管理员权限:建议给超级管理员角色默认分配所有权限,且禁止删除或修改该角色的权限;
-
权限审计:可扩展添加 “权限操作日志” 表,记录角色权限的变更记录(如谁、何时、修改了哪个角色的哪些权限),便于追溯。
综上,DncZeus 的 RBAC 权限设计是一套 “设计简洁、实现高效、扩展性强” 的通用方案,既满足了中小型后台系统的权限管理需求,也为二次开发提供了灵活的扩展接口,特别适合 .NET 开发者快速搭建具备精细化权限控制的后台系统。

浙公网安备 33010602011771号