标题: 权限管理太复杂?RBAC模型帮你搞定一切!
副标题: 从用户到权限,一套完整的权限管理解决方案
🎬 开篇:权限管理的噩梦
项目经理:小王,给张三开通查看财务报表的权限
开发:好的!(直接在代码里加if判断)
一周后:
项目经理:给李四也开通
开发:好的!(再加一个if)
一个月后:
项目经理:所有财务部的人都要有这个权限
开发:💀 我要改100个地方...
三个月后:
项目经理:张三离职了,回收所有权限
开发:😱 他有30个权限,我要查遍整个代码库...
六个月后:
CEO:系统被黑了,数据泄露!谁有查看用户隐私的权限?
开发:这...我也不知道啊...😰
教训:权限管理必须系统化、可配置、可追溯!
🤔 为什么需要RBAC?
想象公司的门禁系统:
- ❌ 直接给权限: 每个人单独配置能进哪些门(管理噩梦)
- ✅ RBAC模式: 员工→角色→权限(总经理能进所有门,前台只能进大厅)
📚 知识地图
RBAC权限管理体系
├── 👤 用户(User)
├── 🎭 角色(Role)
├── 🔑 权限(Permission)
├── 📊 数据权限(Data Permission)
└── 🔗 关系映射
├── 用户-角色(多对多)
├── 角色-权限(多对多)
└── 角色-数据范围
🗄️ 第一章:数据库表设计
核心表结构
-- 1. 用户表
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码(加密)',
real_name VARCHAR(50) COMMENT '真实姓名',
email VARCHAR(100) COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
dept_id BIGINT COMMENT '部门ID',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_username (username),
INDEX idx_dept_id (dept_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 2. 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_code VARCHAR(50) NOT NULL COMMENT '角色编码',
role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
description VARCHAR(200) COMMENT '描述',
data_scope TINYINT NOT NULL DEFAULT 1 COMMENT '数据范围:1-全部,2-本部门及子部门,3-本部门,4-仅本人,5-自定义',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
sort INT NOT NULL DEFAULT 0 COMMENT '排序',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_role_code (role_code),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- 3. 权限表(菜单+按钮)
CREATE TABLE sys_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父级ID',
permission_code VARCHAR(100) NOT NULL COMMENT '权限编码',
permission_name VARCHAR(50) NOT NULL COMMENT '权限名称',
permission_type TINYINT NOT NULL COMMENT '类型:1-菜单,2-按钮',
url VARCHAR(200) COMMENT '菜单URL',
icon VARCHAR(50) COMMENT '图标',
sort INT NOT NULL DEFAULT 0 COMMENT '排序',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_permission_code (permission_code),
INDEX idx_parent_id (parent_id),
INDEX idx_type (permission_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
-- 4. 用户-角色关联表
CREATE TABLE sys_user_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL COMMENT '用户ID',
role_id BIGINT NOT NULL COMMENT '角色ID',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_role (user_id, role_id),
INDEX idx_user_id (user_id),
INDEX idx_role_id (role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
-- 5. 角色-权限关联表
CREATE TABLE sys_role_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_id BIGINT NOT NULL COMMENT '角色ID',
permission_id BIGINT NOT NULL COMMENT '权限ID',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_role_permission (role_id, permission_id),
INDEX idx_role_id (role_id),
INDEX idx_permission_id (permission_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';
-- 6. 部门表(数据权限用)
CREATE TABLE sys_dept (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父部门ID',
dept_name VARCHAR(50) NOT NULL COMMENT '部门名称',
dept_code VARCHAR(50) NOT NULL COMMENT '部门编码',
leader VARCHAR(50) COMMENT '负责人',
phone VARCHAR(20) COMMENT '联系电话',
sort INT NOT NULL DEFAULT 0 COMMENT '排序',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_dept_code (dept_code),
INDEX idx_parent_id (parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表';
-- 7. 角色-部门关联表(自定义数据权限)
CREATE TABLE sys_role_dept (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_id BIGINT NOT NULL COMMENT '角色ID',
dept_id BIGINT NOT NULL COMMENT '部门ID',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_role_dept (role_id, dept_id),
INDEX idx_role_id (role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色部门关联表';
-- 初始化数据
-- 超级管理员
INSERT INTO sys_user (username, password, real_name, status)
VALUES ('admin', '$2a$10$encrypted_password', '超级管理员', 1);
-- 角色
INSERT INTO sys_role (role_code, role_name, data_scope) VALUES
('ADMIN', '超级管理员', 1),
('MANAGER', '部门经理', 2),
('EMPLOYEE', '普通员工', 4);
-- 权限(示例)
INSERT INTO sys_permission (parent_id, permission_code, permission_name, permission_type, url) VALUES
(0, 'system', '系统管理', 1, '/system'),
(1, 'system:user', '用户管理', 1, '/system/user'),
(2, 'system:user:add', '新增用户', 2, NULL),
(2, 'system:user:edit', '编辑用户', 2, NULL),
(2, 'system:user:delete', '删除用户', 2, NULL);
💻 第二章:核心功能实现
功能1:权限校验
/**
* 权限服务
*/
@Service
public class PermissionService {
@Autowired
private UserRoleMapper userRoleMapper;
@Autowired
private RolePermissionMapper rolePermissionMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 检查用户是否有指定权限
*/
public boolean hasPermission(Long userId, String permissionCode) {
// 1. 从缓存获取用户权限
String cacheKey = "user:permissions:" + userId;
Set<String> permissions = (Set<String>) redisTemplate.opsForValue().get(cacheKey);
if (permissions == null) {
// 2. 查询用户的所有权限
permissions = loadUserPermissions(userId);
// 3. 缓存权限(30分钟)
redisTemplate.opsForValue().set(
cacheKey,
permissions,
Duration.ofMinutes(30)
);
}
// 4. 检查权限
boolean hasPermission = permissions.contains(permissionCode);
log.debug("权限校验:userId={}, permission={}, result={}",
userId, permissionCode, hasPermission);
return hasPermission;
}
/**
* 加载用户的所有权限
*/
private Set<String> loadUserPermissions(Long userId) {
// 1. 查询用户的所有角色
List<Long> roleIds = userRoleMapper.selectRoleIdsByUserId(userId);
if (roleIds.isEmpty()) {
return Collections.emptySet();
}
// 2. 查询角色的所有权限
List<String> permissionCodes = rolePermissionMapper
.selectPermissionCodesByRoleIds(roleIds);
return new HashSet<>(permissionCodes);
}
/**
* 清除用户权限缓存(角色变更时调用)
*/
public void clearUserPermissionCache(Long userId) {
String cacheKey = "user:permissions:" + userId;
redisTemplate.delete(cacheKey);
log.info("清除用户权限缓存:userId={}", userId);
}
}
/**
* 权限注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value(); // 权限编码
String message() default "无权限访问";
}
/**
* 权限拦截器(AOP)
*/
@Aspect
@Component
public class PermissionAspect {
@Autowired
private PermissionService permissionService;
@Around("@annotation(requiresPermission)")
public Object checkPermission(ProceedingJoinPoint pjp,
RequiresPermission requiresPermission) throws Throwable {
// 1. 获取当前用户ID
Long userId = UserContext.getCurrentUserId();
if (userId == null) {
throw new UnauthorizedException("未登录");
}
// 2. 检查权限
String permissionCode = requiresPermission.value();
boolean hasPermission = permissionService.hasPermission(userId, permissionCode);
if (!hasPermission) {
throw new ForbiddenException(requiresPermission.message());
}
// 3. 执行方法
return pjp.proceed();
}
}
/**
* 使用示例
*/
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 新增用户(需要权限)
*/
@PostMapping
@RequiresPermission("system:user:add")
public Result addUser(@RequestBody User user) {
// 业务逻辑
return Result.success();
}
/**
* 删除用户(需要权限)
*/
@DeleteMapping("/{id}")
@RequiresPermission("system:user:delete")
public Result deleteUser(@PathVariable Long id) {
// 业务逻辑
return Result.success();
}
}
功能2:数据权限
/**
* 数据权限服务
*/
@Service
public class DataPermissionService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private DeptMapper deptMapper;
/**
* 获取用户的数据权限SQL
*/
public String getDataPermissionSql(Long userId, String tableAlias) {
// 1. 查询用户信息
User user = userMapper.selectById(userId);
if (user == null) {
return " 1=0 "; // 无权限
}
// 2. 查询用户的角色
List<Role> roles = roleMapper.selectByUserId(userId);
if (roles.isEmpty()) {
return " 1=0 ";
}
// 3. 获取最大的数据权限范围
DataScope maxScope = roles.stream()
.map(Role::getDataScope)
.max(Comparator.comparing(DataScope::getLevel))
.orElse(DataScope.SELF);
// 4. 根据数据范围生成SQL
switch (maxScope) {
case ALL:
// 全部数据
return " 1=1 ";
case DEPT_AND_CHILD:
// 本部门及子部门数据
List<Long> deptIds = deptMapper.selectChildDeptIds(user.getDeptId());
deptIds.add(user.getDeptId());
return String.format(" %s.dept_id IN (%s) ",
tableAlias,
deptIds.stream()
.map(String::valueOf)
.collect(Collectors.joining(",")));
case DEPT_ONLY:
// 仅本部门数据
return String.format(" %s.dept_id = %d ", tableAlias, user.getDeptId());
case SELF:
// 仅本人数据
return String.format(" %s.create_user_id = %d ", tableAlias, userId);
case CUSTOM:
// 自定义数据权限
List<Long> customDeptIds = roleMapper.selectCustomDeptIds(
roles.stream()
.map(Role::getId)
.collect(Collectors.toList())
);
if (customDeptIds.isEmpty()) {
return " 1=0 ";
}
return String.format(" %s.dept_id IN (%s) ",
tableAlias,
customDeptIds.stream()
.map(String::valueOf)
.collect(Collectors.joining(",")));
default:
return " 1=0 ";
}
}
}
/**
* 数据权限注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
String tableAlias() default "t"; // 表别名
}
/**
* MyBatis拦截器(实现数据权限过滤)
*/
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
@Component
public class DataPermissionInterceptor implements Interceptor {
@Autowired
private DataPermissionService dataPermissionService;
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
// 1. 获取SQL
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
String originalSql = boundSql.getSql();
// 2. 检查是否需要数据权限过滤
MappedStatement mappedStatement =
(MappedStatement) metaObject.getValue("delegate.mappedStatement");
String methodName = mappedStatement.getId();
Method method = getMethod(methodName);
if (method == null || !method.isAnnotationPresent(DataPermission.class)) {
// 不需要数据权限过滤
return invocation.proceed();
}
// 3. 获取当前用户ID
Long userId = UserContext.getCurrentUserId();
if (userId == null) {
throw new UnauthorizedException("未登录");
}
// 4. 获取表别名
DataPermission annotation = method.getAnnotation(DataPermission.class);
String tableAlias = annotation.tableAlias();
// 5. 生成数据权限SQL
String dataPermissionSql = dataPermissionService
.getDataPermissionSql(userId, tableAlias);
// 6. 修改SQL(在WHERE后面添加数据权限条件)
String newSql = addDataPermissionToSql(originalSql, dataPermissionSql);
// 7. 替换SQL
metaObject.setValue("delegate.boundSql.sql", newSql);
log.debug("数据权限SQL:{}", dataPermissionSql);
return invocation.proceed();
}
/**
* 在SQL中添加数据权限条件
*/
private String addDataPermissionToSql(String originalSql, String dataPermissionSql) {
// 简化实现:在WHERE后面添加AND条件
// 实际应使用SQL解析器(如JSqlParser)
if (originalSql.toUpperCase().contains("WHERE")) {
return originalSql.replaceFirst(
"(?i)WHERE",
"WHERE " + dataPermissionSql + " AND "
);
} else {
return originalSql + " WHERE " + dataPermissionSql;
}
}
private Method getMethod(String methodName) {
try {
String className = methodName.substring(0, methodName.lastIndexOf("."));
String methodSimpleName = methodName.substring(methodName.lastIndexOf(".") + 1);
Class<?> clazz = Class.forName(className);
return Arrays.stream(clazz.getMethods())
.filter(m -> m.getName().equals(methodSimpleName))
.findFirst()
.orElse(null);
} catch (Exception e) {
return null;
}
}
}
/**
* 使用示例
*/
@Mapper
public interface UserMapper {
/**
* 查询用户列表(带数据权限)
*/
@Select("SELECT * FROM sys_user t WHERE t.status = 1")
@DataPermission(tableAlias = "t")
List<User> selectList();
}
/**
* SQL执行示例:
*
* 原始SQL:
* SELECT * FROM sys_user t WHERE t.status = 1
*
* 添加数据权限后(假设是部门经理):
* SELECT * FROM sys_user t
* WHERE t.dept_id IN (100, 101, 102) AND t.status = 1
*
* 效果:只能查询本部门及子部门的用户 ✅
*/
功能3:角色管理
/**
* 角色服务
*/
@Service
public class RoleService {
@Autowired
private RoleMapper roleMapper;
@Autowired
private RolePermissionMapper rolePermissionMapper;
@Autowired
private UserRoleMapper userRoleMapper;
@Autowired
private PermissionService permissionService;
/**
* 创建角色
*/
@Transactional(rollbackFor = Exception.class)
public Long createRole(RoleDTO dto) {
// 1. 检查角色编码是否重复
Role existing = roleMapper.selectByRoleCode(dto.getRoleCode());
if (existing != null) {
throw new BusinessException("角色编码已存在");
}
// 2. 创建角色
Role role = Role.builder()
.roleCode(dto.getRoleCode())
.roleName(dto.getRoleName())
.description(dto.getDescription())
.dataScope(dto.getDataScope())
.status(1)
.build();
roleMapper.insert(role);
// 3. 分配权限
if (dto.getPermissionIds() != null && !dto.getPermissionIds().isEmpty()) {
assignPermissions(role.getId(), dto.getPermissionIds());
}
log.info("创建角色成功:roleId={}, roleCode={}", role.getId(), role.getRoleCode());
return role.getId();
}
/**
* 分配权限
*/
@Transactional(rollbackFor = Exception.class)
public void assignPermissions(Long roleId, List<Long> permissionIds) {
// 1. 删除旧的权限关联
rolePermissionMapper.deleteByRoleId(roleId);
// 2. 插入新的权限关联
if (permissionIds != null && !permissionIds.isEmpty()) {
List<RolePermission> list = permissionIds.stream()
.map(permissionId -> RolePermission.builder()
.roleId(roleId)
.permissionId(permissionId)
.build())
.collect(Collectors.toList());
rolePermissionMapper.batchInsert(list);
}
// 3. 清除该角色下所有用户的权限缓存
List<Long> userIds = userRoleMapper.selectUserIdsByRoleId(roleId);
for (Long userId : userIds) {
permissionService.clearUserPermissionCache(userId);
}
log.info("分配权限成功:roleId={}, permissionCount={}",
roleId, permissionIds.size());
}
/**
* 分配用户
*/
@Transactional(rollbackFor = Exception.class)
public void assignUsers(Long roleId, List<Long> userIds) {
// 1. 删除旧的用户关联
userRoleMapper.deleteByRoleId(roleId);
// 2. 插入新的用户关联
if (userIds != null && !userIds.isEmpty()) {
List<UserRole> list = userIds.stream()
.map(userId -> UserRole.builder()
.userId(userId)
.roleId(roleId)
.build())
.collect(Collectors.toList());
userRoleMapper.batchInsert(list);
// 3. 清除用户权限缓存
for (Long userId : userIds) {
permissionService.clearUserPermissionCache(userId);
}
}
log.info("分配用户成功:roleId={}, userCount={}", roleId, userIds.size());
}
/**
* 删除角色
*/
@Transactional(rollbackFor = Exception.class)
public void deleteRole(Long roleId) {
// 1. 检查是否有用户关联
long userCount = userRoleMapper.countByRoleId(roleId);
if (userCount > 0) {
throw new BusinessException("该角色下还有用户,无法删除");
}
// 2. 删除角色-权限关联
rolePermissionMapper.deleteByRoleId(roleId);
// 3. 删除角色
roleMapper.deleteById(roleId);
log.info("删除角色成功:roleId={}", roleId);
}
}
🎯 第三章:前端集成
菜单权限
<!-- MenuTree.vue -->
<template>
<el-menu>
<template v-for="menu in menuTree">
<!-- 一级菜单 -->
<el-submenu
v-if="menu.children && menu.children.length > 0"
:key="menu.id"
:index="menu.id.toString()">
<template #title>
<i :class="menu.icon"></i>
<span>{{ menu.name }}</span>
</template>
<!-- 子菜单 -->
<el-menu-item
v-for="child in menu.children"
:key="child.id"
:index="child.url">
{{ child.name }}
</el-menu-item>
</el-submenu>
<!-- 无子菜单 -->
<el-menu-item
v-else
:key="menu.id"
:index="menu.url">
<i :class="menu.icon"></i>
<span>{{ menu.name }}</span>
</el-menu-item>
</template>
</el-menu>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getUserMenuTree } from '@/api/permission'
const menuTree = ref([])
onMounted(async () => {
// 获取当前用户的菜单树
const response = await getUserMenuTree()
menuTree.value = response.data
})
</script>
按钮权限
<!-- UserList.vue -->
<template>
<div>
<el-table :data="userList">
<el-table-column prop="username" label="用户名" />
<el-table-column prop="realName" label="姓名" />
<el-table-column label="操作">
<template #default="{ row }">
<!-- v-permission指令控制按钮显示 -->
<el-button
v-permission="'system:user:edit'"
type="primary"
@click="handleEdit(row)">
编辑
</el-button>
<el-button
v-permission="'system:user:delete'"
type="danger"
@click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
// permission指令
import { useUserStore } from '@/stores/user'
// 自定义指令
app.directive('permission', {
mounted(el, binding) {
const userStore = useUserStore()
const permissionCode = binding.value
// 检查是否有权限
if (!userStore.permissions.includes(permissionCode)) {
// 没有权限,移除元素
el.parentNode?.removeChild(el)
}
}
})
</script>
✅ 最佳实践
设计原则:
□ 最小权限原则(用户只有必需的权限)
□ 职责分离原则(权限不能过度集中)
□ 权限可追溯(记录权限变更日志)
□ 默认拒绝原则(无权限默认拒绝访问)
性能优化:
□ 权限信息缓存到Redis(减少数据库查询)
□ 用户登录时加载权限(避免每次查询)
□ 菜单树缓存(前端缓存菜单结构)
□ 数据权限SQL优化(建立合适的索引)
安全加固:
□ 权限变更需要审批流程
□ 记录所有权限操作日志
□ 定期审计用户权限
□ 敏感操作二次验证
用户体验:
□ 无权限时友好提示
□ 权限不足时不显示按钮
□ 提供权限申请流程
□ 管理员可以临时授权
🎉 总结
核心要点
RBAC权限管理核心:
1️⃣ 五张核心表
- 用户表
- 角色表
- 权限表
- 用户角色关联表
- 角色权限关联表
2️⃣ 两种权限
- 功能权限(菜单+按钮)
- 数据权限(部门、本人等)
3️⃣ 三个关键点
- 权限缓存(Redis)
- 数据权限(MyBatis拦截器)
- 前后端联动(菜单树+按钮权限)
4️⃣ 四大原则
- 最小权限
- 职责分离
- 默认拒绝
- 可追溯
📚 延伸阅读
记住:好的权限系统是"灵活、安全、高效"的完美结合! 🔐
文档编写时间:2025年10月24日
作者:热爱安全的权限工程师
版本:v1.0
愿你的系统固若金汤! 🔐✨