RBAC 权限模型设计
约 1459 字大约 5 分钟
布欧-Lewyon
2026-05-16
首页 › Spring Boot › 安全入门 › RBAC 权限模型设计
RBAC(Role-Based Access Control)是业界最广泛使用的权限模型——权限不直接授予用户,而是通过"用户 → 角色 → 权限"的间接关系管理。Spring Security 的 hasRole / hasAuthority 本质就是 RBAC 的两层抽象,但要支撑企业级动态权限管理,需要将角色与权限存储到数据库并动态加载。
RBAC 核心模型
数据库设计
-- 用户表
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE
);
-- 角色表
CREATE TABLE roles (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE, -- ROLE_ADMIN, ROLE_USER
description VARCHAR(255)
);
-- 用户-角色关联
CREATE TABLE user_roles (
user_id BIGINT NOT NULL REFERENCES users(id),
role_id BIGINT NOT NULL REFERENCES roles(id),
PRIMARY KEY (user_id, role_id)
);
-- 权限表(粗粒度操作:read / write / delete)
CREATE TABLE permissions (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE -- user:read, order:write
);
-- 角色-权限关联
CREATE TABLE role_permissions (
role_id BIGINT NOT NULL REFERENCES roles(id),
permission_id BIGINT NOT NULL REFERENCES permissions(id),
PRIMARY KEY (role_id, permission_id)
);Spring Security 动态加载
查询用户与权限
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(username));
// 查询用户的角色和权限
List<String> roles = userRepository.findRoleNamesByUserId(user.getId());
List<String> permissions = userRepository.findPermissionNamesByUserId(user.getId());
// Spring Security 将角色视为特殊权限(自动加 ROLE_ 前缀)
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 添加角色
roles.stream()
.map(r -> new SimpleGrantedAuthority("ROLE_" + r))
.forEach(authorities::add);
// 添加权限
permissions.stream()
.map(SimpleGrantedAuthority::new)
.forEach(authorities::add);
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(),
user.isEnabled(), true, true, true,
authorities);
}
}表的查询
@Mapper
public interface UserRepository {
@Select("SELECT r.name FROM roles r " +
"JOIN user_roles ur ON r.id = ur.role_id " +
"WHERE ur.user_id = #{userId}")
List<String> findRoleNamesByUserId(@Param("userId") Long userId);
@Select("SELECT DISTINCT p.name FROM permissions p " +
"JOIN role_permissions rp ON p.id = rp.permission_id " +
"JOIN user_roles ur ON rp.role_id = ur.role_id " +
"WHERE ur.user_id = #{userId}")
List<String> findPermissionNamesByUserId(@Param("userId") Long userId);
}URL 级别权限控制
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// 基于角色的 URL 保护
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/manager/**").hasAnyRole("ADMIN", "MANAGER")
// 基于权限的 URL 保护(更细粒度)
.requestMatchers(HttpMethod.GET, "/api/orders/**").hasAuthority("order:read")
.requestMatchers(HttpMethod.POST, "/api/orders/**").hasAuthority("order:write")
.requestMatchers(HttpMethod.DELETE, "/api/orders/**").hasAuthority("order:delete")
.anyRequest().authenticated()
);
return http.build();
}
}方法级别权限控制
@SpringBootApplication
@EnableMethodSecurity
public class DemoApplication { ... }
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping
@PreAuthorize("hasAuthority('order:read')")
public Result<List<Order>> list() { ... }
@PostMapping
@PreAuthorize("hasAuthority('order:write')")
public Result<Order> create(@RequestBody Order order) { ... }
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('order:delete') AND @permCheck.isOwner(#id, authentication.name)")
public Result<Void> delete(@PathVariable Long id) { ... }
}
@PreAuthorize支持 SpEL 表达式,可以组合多个条件:hasRole('ADMIN') OR hasAuthority('order:delete')。也支持调用 Spring Bean 方法——@permCheck.isOwner(#id, authentication.name)将更复杂的校验逻辑委托给 Service 层。
权限表达式速查
| 表达式 | 说明 |
|---|---|
hasRole('ADMIN') | 当前用户是否有 ADMIN 角色(自动加 ROLE_ 前缀匹配) |
hasAnyRole('ADMIN','MANAGER') | 是否有任一角色 |
hasAuthority('order:write') | 是否有指定权限(精确匹配) |
hasAnyAuthority(...) | 是否有任一权限 |
permitAll() / denyAll() | 全部允许 / 全部拒绝 |
isAuthenticated() / isAnonymous() | 是否已认证 / 匿名 |
#param.field == authentication.name | 数据级权限——只有资源属主可操作 |
角色继承
复杂业务场景中,角色之间存在层级关系(如管理员继承经理的所有权限,经理继承用户的所有权限),可通过 RoleHierarchy 配置:
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_MANAGER > ROLE_USER");
return hierarchy;
}
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler(
RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler handler =
new DefaultMethodSecurityExpressionHandler();
handler.setRoleHierarchy(roleHierarchy);
return handler;
}配置后,hasRole('USER') 匹配时,ADMIN 和 MANAGER 也会通过——无需为每个角色单独赋权。
RBAC 与 ABAC
| 模型 | 授权依据 | 灵活性 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| RBAC | 角色 + 权限 | 中(需提前定义角色) | 低 | 企业管理系统、后台 |
| ABAC | 用户属性 + 资源属性 + 环境 | 高(运行时评估) | 高 | 金融风控、数据脱敏 |
ABAC 示例:"仅当用户所在部门与资源所属部门相同且当前时间在工作时间范围内时允许访问"——这种场景 RBAC 力不从心,需自定义
PermissionEvaluator或引入 Spring Security ACL。
常见权限设计误区
- 将权限直接分配给用户 —— 违背 RBAC 核心原则。用户数量增多后,权限管理会变得不可维护。始终通过角色间接分配。
- 角色数量膨胀 —— 每个业务场景建一个新角色(如"订单审核员""订单退款员")。建议角色抽象到组织层级(如 ADMIN、MANAGER、OPERATOR),细粒度用权限区分。
- 前端菜单与后端权限分离 —— 前端仅根据角色渲染菜单,后端仅校验权限。带来问题是:前端隐藏了按钮但后端未校验——用户可以绕过 UI 直接发请求。前后端必须同时校验。
- 权限数据缓存不及时 —— 用户权限变更后,内存中的
UserDetails仍然旧值。建议:- 用户重新登录
- 或调用
SecurityContextHolder.clearContext() - 或使用
@CacheEvict清除用户缓存
小结
- RBAC 核心:用户 → 角色 → 权限,通过
GrantedAuthority加载到 Spring Security 上下文中。 - URL 级别保护用
requestMatchers().hasRole()/.hasAuthority();方法级别用@PreAuthorize。 RoleHierarchy实现角色继承,减少重复赋权。- ABAC 是 RBAC 的补充,适用于动态策略场景。
- 易错:
hasRole('ADMIN')自动补全为ROLE_ADMIN,而hasAuthority('ROLE_ADMIN')不会自动补全——不要在hasAuthority中使用ROLE_以外的前缀混淆二者。Spring Security 的@PostAuthorize和@PostFilter可以在方法返回后校验或过滤结果数据,适用于行级数据权限。 - 思考任务:设计用户-角色-权限三张表,实现从数据库动态加载用户权限的
UserDetailsService;通过@PreAuthorize("hasAuthority('order:write')")保护订单创建接口,验证不同的角色是否能正确被拦截或放行。
上一节:OAuth2.0 集成
下一节:生产部署
