目录
- RBAC0 模型核心概念
- 数据库表结构设计
- 工作流程详解
- Java 实现示例(含详细中文注释)
- API 使用示例
- 测试用例验证
一、RBAC0 模型核心概念
1.1 什么是 RBAC0?
RBAC0(Role-Based Access Control Level 0)是 RBAC 模型的基础版本,包含三个核心实体:
- 用户(User):系统使用者
- 角色(Role):权限的集合,代表某种职能
- 权限(Permission):对资源的操作能力
1.2 核心关系
- 用户 ↔ 角色:多对多关系(一个用户可拥有多个角色,一个角色可分配给多个用户)
- 角色 ↔ 权限:多对多关系(一个角色可拥有多个权限,一个权限可分配给多个角色)
1.3 形象类比
公司组织架构:
- 用户 = 员工(张三、李四)
- 角色 = 职位(开发工程师、产品经理、HR)
- 权限 = 工作职责(写代码、查看需求文档、管理员工信息)
当新员工入职时,只需分配对应职位(角色),自动获得该职位的所有工作职责(权限)
二、数据库表结构设计
2.1 表结构总览
| 表名 | 说明 | 关系 |
|---|---|---|
sys_user | 用户表 | - |
sys_role | 角色表 | - |
sys_permission | 权限表 | - |
sys_user_role | 用户-角色关联表 | 用户 ↔ 角色 |
sys_role_permission | 角色-权限关联表 | 角色 ↔ 权限 |
2.2 详细表结构
2.2.1 用户表 (sys_user)
CREATE TABLE `sys_user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL COMMENT '用户名(唯一)',
`password` VARCHAR(100) NOT NULL COMMENT '密码(加密存储)',
`real_name` VARCHAR(50) NOT NULL COMMENT '真实姓名',
`email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
`phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号',
`status` TINYINT DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
2.2.2 角色表 (sys_role)
CREATE TABLE `sys_role` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_code` VARCHAR(50) NOT NULL COMMENT '角色编码(唯一,用于程序识别)',
`role_name` VARCHAR(50) NOT NULL COMMENT '角色名称(用于显示)',
`description` VARCHAR(200) DEFAULT NULL COMMENT '角色描述',
`status` TINYINT DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
2.2.3 权限表 (sys_permission)
CREATE TABLE `sys_permission` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`permission_code` VARCHAR(100) NOT NULL COMMENT '权限编码(唯一,用于程序识别)',
`permission_name` VARCHAR(50) NOT NULL COMMENT '权限名称(用于显示)',
`resource_type` VARCHAR(20) NOT NULL COMMENT '资源类型:MENU-菜单,BUTTON-按钮,API-接口',
`resource_path` VARCHAR(200) DEFAULT NULL COMMENT '资源路径(如:/api/user/list)',
`description` VARCHAR(200) DEFAULT NULL COMMENT '权限描述',
`status` TINYINT DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_permission_code` (`permission_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
2.2.4 用户-角色关联表 (sys_user_role)
CREATE TABLE `sys_user_role` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '关联ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`role_id` BIGINT NOT NULL COMMENT '角色ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_role` (`user_id`, `role_id`), -- 防止重复分配
KEY `idx_user_id` (`user_id`),
KEY `idx_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户-角色关联表';
2.2.5 角色-权限关联表 (sys_role_permission)
CREATE TABLE `sys_role_permission` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '关联ID',
`role_id` BIGINT NOT NULL COMMENT '角色ID',
`permission_id` BIGINT NOT NULL COMMENT '权限ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_permission` (`role_id`, `permission_id`), -- 防止重复分配
KEY `idx_role_id` (`role_id`),
KEY `idx_permission_id` (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色-权限关联表';
2.3 示例数据
-- 插入用户
INSERT INTO sys_user (username, password, real_name, email)
VALUES ('zhangsan', 'encrypted_password', '张三', 'zhangsan@example.com');
-- 插入角色
INSERT INTO sys_role (role_code, role_name, description)
VALUES ('DEVELOPER', '开发工程师', '负责系统开发'),
('PRODUCT_MANAGER', '产品经理', '负责产品需求管理');
-- 插入权限
INSERT INTO sys_permission (permission_code, permission_name, resource_type, resource_path)
VALUES ('USER_VIEW', '查看用户', 'API', '/api/user/list'),
('USER_EDIT', '编辑用户', 'API', '/api/user/update'),
('PRODUCT_VIEW', '查看产品', 'API', '/api/product/list');
-- 用户-角色关联(张三拥有开发工程师角色)
INSERT INTO sys_user_role (user_id, role_id)
VALUES (1, 1);
-- 角色-权限关联(开发工程师拥有查看用户权限)
INSERT INTO sys_role_permission (role_id, permission_id)
VALUES (1, 1);
三、RBAC0 工作流程详解
3.1 权限分配流程
3.2 权限验证流程
3.3 核心验证逻辑
- 获取当前用户ID
- 查询用户关联的所有角色
- 查询这些角色关联的所有权限
- 检查目标权限是否在权限列表中
四、Java 实现示例(含详细中文注释)
4.1 实体类定义
用户实体
/**
* 用户实体类
* 对应数据库表:sys_user
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
/**
* 用户ID,主键
*/
private Long id;
/**
* 用户名,唯一标识,用于登录
*/
private String username;
/**
* 密码,加密存储
*/
private String password;
/**
* 真实姓名,用于显示
*/
private String realName;
/**
* 邮箱地址
*/
private String email;
/**
* 手机号码
*/
private String phone;
/**
* 用户状态:1-正常,0-禁用
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
角色实体
/**
* 角色实体类
* 对应数据库表:sys_role
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Role {
/**
* 角色ID,主键
*/
private Long id;
/**
* 角色编码,唯一标识,用于程序中识别角色
* 例如:ADMIN, DEVELOPER, PRODUCT_MANAGER
*/
private String roleCode;
/**
* 角色名称,用于界面显示
* 例如:管理员、开发工程师、产品经理
*/
private String roleName;
/**
* 角色描述,说明角色的职责
*/
private String description;
/**
* 角色状态:1-正常,0-禁用
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
权限实体
/**
* 权限实体类
* 对应数据库表:sys_permission
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Permission {
/**
* 权限ID,主键
*/
private Long id;
/**
* 权限编码,唯一标识,用于程序中识别权限
* 例如:USER_VIEW, USER_EDIT, PRODUCT_VIEW
*/
private String permissionCode;
/**
* 权限名称,用于界面显示
* 例如:查看用户、编辑用户、查看产品
*/
private String permissionName;
/**
* 资源类型:
* MENU - 菜单权限
* BUTTON - 按钮权限
* API - 接口权限
*/
private String resourceType;
/**
* 资源路径,对于API权限就是具体的URL路径
* 例如:/api/user/list, /api/product/update
*/
private String resourcePath;
/**
* 权限描述,说明权限的具体作用
*/
private String description;
/**
* 权限状态:1-正常,0-禁用
*/
private Integer status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
4.2 Mapper 接口(MyBatis)
用户角色关联查询
/**
* 用户Mapper接口
* 负责用户相关的数据库操作
*/
@Mapper
public interface UserMapper {
/**
* 根据用户ID查询用户基本信息
* @param userId 用户ID
* @return 用户对象
*/
User selectById(Long userId);
/**
* 根据用户名查询用户(用于登录)
* @param username 用户名
* @return 用户对象
*/
User selectByUsername(String username);
/**
* 根据用户ID查询用户关联的所有角色ID列表
* 通过 sys_user_role 关联表查询
* @param userId 用户ID
* @return 角色ID列表
*/
List<Long> selectRoleIdsByUserId(Long userId);
}
角色权限关联查询
/**
* 角色Mapper接口
* 负责角色相关的数据库操作
*/
@Mapper
public interface RoleMapper {
/**
* 根据角色ID列表查询角色基本信息
* @param roleIds 角色ID列表
* @return 角色列表
*/
List<Role> selectByIds(@Param("roleIds") List<Long> roleIds);
/**
* 根据角色ID列表查询这些角色关联的所有权限ID列表
* 通过 sys_role_permission 关联表查询
* @param roleIds 角色ID列表
* @return 权限ID列表
*/
List<Long> selectPermissionIdsByRoleIds(@Param("roleIds") List<Long> roleIds);
}
权限查询
/**
* 权限Mapper接口
* 负责权限相关的数据库操作
*/
@Mapper
public interface PermissionMapper {
/**
* 根据权限ID列表查询权限基本信息
* @param permissionIds 权限ID列表
* @return 权限列表
*/
List<Permission> selectByIds(@Param("permissionIds") List<Long> permissionIds);
/**
* 根据权限编码查询权限
* @param permissionCode 权限编码
* @return 权限对象
*/
Permission selectByCode(String permissionCode);
}
4.3 权限服务核心实现
/**
* RBAC0权限服务实现类
* 负责权限的核心业务逻辑处理
*/
@Service
@Slf4j
public class Rbac0PermissionService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private PermissionMapper permissionMapper;
/**
* 检查用户是否拥有指定权限
* 这是RBAC0模型的核心验证方法
*
* 验证流程:
* 1. 根据用户ID获取用户关联的角色ID列表
* 2. 根据角色ID列表获取这些角色关联的权限ID列表
* 3. 根据权限ID列表获取权限编码列表
* 4. 检查目标权限编码是否在权限编码列表中
*
* @param userId 用户ID
* @param permissionCode 目标权限编码(如:USER_VIEW)
* @return true-拥有权限,false-无权限
*/
public boolean hasPermission(Long userId, String permissionCode) {
log.info("开始检查用户权限: userId={}, permissionCode={}", userId, permissionCode);
try {
// 步骤1: 获取用户关联的角色ID列表
List<Long> roleIds = userMapper.selectRoleIdsByUserId(userId);
log.debug("用户关联的角色ID列表: {}", roleIds);
// 如果用户没有任何角色,直接返回无权限
if (CollectionUtils.isEmpty(roleIds)) {
log.warn("用户没有分配任何角色,userId={}", userId);
return false;
}
// 步骤2: 获取角色关联的权限ID列表
List<Long> permissionIds = roleMapper.selectPermissionIdsByRoleIds(roleIds);
log.debug("角色关联的权限ID列表: {}", permissionIds);
// 如果角色没有任何权限,直接返回无权限
if (CollectionUtils.isEmpty(permissionIds)) {
log.warn("角色没有分配任何权限,roleIds={}", roleIds);
return false;
}
// 步骤3: 获取权限编码列表
List<Permission> permissions = permissionMapper.selectByIds(permissionIds);
Set<String> permissionCodes = permissions.stream()
.map(Permission::getPermissionCode)
.collect(Collectors.toSet());
log.debug("用户拥有的权限编码列表: {}", permissionCodes);
// 步骤4: 检查目标权限是否存在
boolean hasPermission = permissionCodes.contains(permissionCode);
log.info("权限检查结果: userId={}, permissionCode={}, result={}",
userId, permissionCode, hasPermission);
return hasPermission;
} catch (Exception e) {
log.error("权限检查发生异常", e);
// 异常情况下默认返回无权限,保证安全性
return false;
}
}
/**
* 获取用户拥有的所有权限编码列表
* 通常用于前端菜单渲染或批量权限检查
*
* @param userId 用户ID
* @return 权限编码集合
*/
public Set<String> getUserPermissionCodes(Long userId) {
log.info("获取用户权限列表: userId={}", userId);
try {
// 获取用户角色ID列表
List<Long> roleIds = userMapper.selectRoleIdsByUserId(userId);
if (CollectionUtils.isEmpty(roleIds)) {
return Collections.emptySet();
}
// 获取权限ID列表
List<Long> permissionIds = roleMapper.selectPermissionIdsByRoleIds(roleIds);
if (CollectionUtils.isEmpty(permissionIds)) {
return Collections.emptySet();
}
// 获取权限编码集合
List<Permission> permissions = permissionMapper.selectByIds(permissionIds);
return permissions.stream()
.map(Permission::getPermissionCode)
.collect(Collectors.toSet());
} catch (Exception e) {
log.error("获取用户权限列表发生异常", e);
return Collections.emptySet();
}
}
/**
* 批量检查用户是否拥有多个权限
* 用于需要同时检查多个权限的场景
*
* @param userId 用户ID
* @param permissionCodes 权限编码列表
* @return 权限检查结果映射(权限编码 -> 是否拥有)
*/
public Map<String, Boolean> checkPermissions(Long userId, List<String> permissionCodes) {
// 先获取用户所有权限
Set<String> userPermissions = getUserPermissionCodes(userId);
// 构建结果映射
Map<String, Boolean> result = new HashMap<>();
for (String permissionCode : permissionCodes) {
result.put(permissionCode, userPermissions.contains(permissionCode));
}
return result;
}
}
4.4 权限拦截器实现
/**
* 权限拦截器
* 在请求到达Controller之前进行权限验证
*/
@Component
public class PermissionInterceptor implements HandlerInterceptor {
@Autowired
private Rbac0PermissionService permissionService;
/**
* 在请求处理之前进行权限检查
*
* @param request 当前HTTP请求
* @param response HTTP响应
* @param handler 处理器(Controller方法)
* @return true-放行,false-拦截
* @throws Exception 异常
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 1. 获取当前登录用户ID(假设已通过认证)
Long currentUserId = getCurrentUserId(request);
if (currentUserId == null) {
// 未登录用户,返回401
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("未登录");
return false;
}
// 2. 获取当前请求需要的权限编码
// 这里通过自定义注解@RequirePermission获取
String requiredPermission = getRequiredPermission(handler);
if (requiredPermission == null) {
// 没有权限要求,直接放行
return true;
}
// 3. 检查用户是否拥有该权限
boolean hasPermission = permissionService.hasPermission(currentUserId, requiredPermission);
if (!hasPermission) {
// 无权限,返回403
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("无权访问");
return false;
}
// 4. 有权限,放行
return true;
}
/**
* 从请求中获取当前用户ID
* 实际项目中通常从JWT Token或Session中获取
*/
private Long getCurrentUserId(HttpServletRequest request) {
// 简化实现:从请求头获取
String userIdStr = request.getHeader("X-User-Id");
if (StringUtils.hasText(userIdStr)) {
try {
return Long.parseLong(userIdStr);
} catch (NumberFormatException e) {
return null;
}
}
return null;
}
/**
* 从处理器(Controller方法)中获取需要的权限编码
* 通过解析@RequirePermission注解实现
*/
private String getRequiredPermission(Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequirePermission annotation = handlerMethod.getMethodAnnotation(RequirePermission.class);
if (annotation != null) {
return annotation.value();
}
}
return null;
}
}
4.5 自定义权限注解
/**
* 权限要求注解
* 用于标记Controller方法需要的权限
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequirePermission {
/**
* 需要的权限编码
* 例如:@RequirePermission("USER_VIEW")
*/
String value();
}
五、API 使用示例
5.1 Controller 层使用
/**
* 用户管理Controller
* 演示如何使用权限注解
*/
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 查询用户列表接口
* 需要 USER_VIEW 权限
*/
@GetMapping("/list")
@RequirePermission("USER_VIEW") // 添加权限注解
public Result<List<UserDTO>> getUserList() {
List<UserDTO> users = userService.getAllUsers();
return Result.success(users);
}
/**
* 更新用户信息接口
* 需要 USER_EDIT 权限
*/
@PostMapping("/update")
@RequirePermission("USER_EDIT") // 添加权限注解
public Result<Void> updateUser(@RequestBody UpdateUserRequest request) {
userService.updateUser(request);
return Result.success();
}
/**
* 获取当前用户信息接口
* 不需要特殊权限,所有登录用户都可以访问
*/
@GetMapping("/current")
public Result<UserDTO> getCurrentUser() {
UserDTO currentUser = userService.getCurrentUser();
return Result.success(currentUser);
}
}
5.2 服务层权限检查
/**
* 用户服务实现类
* 在服务层也可以进行权限检查
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private Rbac0PermissionService permissionService;
@Override
public void deleteUser(Long userId, Long operatorId) {
// 在删除操作前检查操作者权限
if (!permissionService.hasPermission(operatorId, "USER_DELETE")) {
throw new AccessDeniedException("无权删除用户");
}
// 执行删除逻辑
userMapper.deleteById(userId);
}
}
六、测试用例验证
6.1 单元测试
/**
* RBAC0权限服务测试类
*/
@SpringBootTest
class Rbac0PermissionServiceTest {
@Autowired
private Rbac0PermissionService permissionService;
@Test
void testHasPermission_WhenUserHasPermission_ShouldReturnTrue() {
// 准备数据:用户ID=1,拥有USER_VIEW权限
// 执行权限检查
boolean result = permissionService.hasPermission(1L, "USER_VIEW");
// 验证结果
assertTrue(result, "用户应该拥有USER_VIEW权限");
}
@Test
void testHasPermission_WhenUserHasNoPermission_ShouldReturnFalse() {
// 准备数据:用户ID=1,没有USER_EDIT权限
// 执行权限检查
boolean result = permissionService.hasPermission(1L, "USER_EDIT");
// 验证结果
assertFalse(result, "用户不应该拥有USER_EDIT权限");
}
@Test
void testGetUserPermissionCodes_ShouldReturnCorrectPermissions() {
// 执行获取权限列表
Set<String> permissions = permissionService.getUserPermissionCodes(1L);
// 验证结果
assertTrue(permissions.contains("USER_VIEW"), "应该包含USER_VIEW权限");
assertFalse(permissions.contains("USER_EDIT"), "不应该包含USER_EDIT权限");
assertEquals(1, permissions.size(), "应该只有1个权限");
}
}
6.2 集成测试场景
场景1:正常权限访问
- 用户:张三(ID=1)
- 角色:开发工程师(ID=1)
- 权限:USER_VIEW(ID=1)
- 请求:GET /api/user/list
- 预期:返回200,成功获取用户列表
场景2:无权限访问
- 用户:张三(ID=1)
- 角色:开发工程师(ID=1)
- 权限:USER_VIEW(ID=1)
- 请求:POST /api/user/update
- 预期:返回403,无权访问
场景3:用户无角色
- 用户:李四(ID=2)
- 角色:无
- 请求:GET /api/user/list
- 预期:返回403,无权访问
总结
RBAC0 模型虽然简单,但却是权限系统的基础。通过合理的数据库设计和清晰的工作流程,可以构建一个安全、可维护的权限控制系统。关键要点:
- 数据库设计:五张核心表,通过关联表实现多对多关系
- 权限验证:用户 → 角色 → 权限的链式查询
- 安全原则:后端必须进行权限验证,不能依赖前端控制
- 性能考虑:适当使用缓存,避免频繁数据库查询
- 扩展性:为后续升级到 RBAC1/RBAC2 预留设计空间
这个实现方案可以直接应用于中小型项目,对于大型项目可以在此基础上增加缓存、异步处理等优化措施。
1548

被折叠的 条评论
为什么被折叠?



