RBAC (基于角色的访问控制)
RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。
RBAC 核心实现
1. 数据库设计
users roles permissions
------- ------ ------------
id id id
username name name
password description url
... method
description
user_roles role_permissions
----------- ---------------
user_id role_id
role_id permission_id
2. Spring Security 配置
@Configuration
@EnableWebSecurity
public class RbacSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.logout();
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
// 从数据库加载用户及其角色
return username -> {
User user = userRepository.findByUsername(username);
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles().stream().map(Role::getName).toArray(String[]::new))
.build();
};
}
}
3. 动态 RBAC 实现
@Service
public class DynamicRbacService {
public boolean hasPermission(Authentication authentication, String url, String method) {
return authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.anyMatch(permission -> {
// 从数据库或缓存检查权限
return permissionRepository.existsByRoleAndUrlAndMethod(
permission, url, method);
});
}
}
ABAC (基于属性的访问控制)
ABAC (Attribute-Based Access Control) 是更灵活的权限模型,它基于用户属性、资源属性、环境属性等做决策。
ABAC 核心实现
1. 策略规则示例
{
"name": "编辑文档策略",
"description": "允许文档所有者在工作时间内编辑自己的文档",
"target": {
"resource.type": "document",
"action": "edit"
},
"condition": {
"user.id == resource.owner_id",
"time >= '09:00' && time <= '18:00'"
},
"effect": "allow"
}
2. Spring Security ABAC 实现
@Component
public class AbacPermissionEvaluator implements PermissionEvaluator {
@Autowired
private PolicyEnforcement policyEnforcement;
@Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject,
Object permission) {
// 构建ABAC请求上下文
AbacRequest request = new AbacRequest(
authentication,
targetDomainObject,
permission.toString()
);
return policyEnforcement.check(request);
}
// 其他必要方法...
}
@Service
public class PolicyEnforcement {
@Autowired
private PolicyRepository policyRepository;
public boolean check(AbacRequest request) {
// 获取所有相关策略
List<Policy> policies = policyRepository.findRelevantPolicies(
request.getSubject(),
request.getResource(),
request.getAction()
);
// 评估策略
return policies.stream().anyMatch(policy ->
evaluatePolicy(policy, request));
}
private boolean evaluatePolicy(Policy policy, AbacRequest request) {
// 实现策略评估逻辑
return true; // 简化示例
}
}
3. 在控制器中使用
@RestController
@RequestMapping("/documents")
public class DocumentController {
@PreAuthorize("hasPermission(#id, 'document', 'read')")
@GetMapping("/{id}")
public Document getDocument(@PathVariable Long id) {
// ...
}
@PreAuthorize("hasPermission(#document, 'edit')")
@PutMapping("/{id}")
public Document updateDocument(@PathVariable Long id,
@RequestBody Document document) {
// ...
}
}
RBAC 与 ABAC 对比
特性 | RBAC | ABAC |
---|---|---|
控制粒度 | 粗粒度(基于角色) | 细粒度(基于属性) |
灵活性 | 较低 | 高 |
实现复杂度 | 简单 | 复杂 |
适用场景 | 角色明确的系统 | 需要复杂权限规则的系统 |
动态调整 | 需要修改角色分配 | 只需修改策略规则 |
性能 | 高 | 相对较低(需评估复杂规则) |
混合实现方案
在实际项目中,可以结合两种模型的优势:
@Service
public class HybridAccessService {
@Autowired
private RbacService rbacService;
@Autowired
private AbacService abacService;
public boolean checkAccess(Authentication auth,
HttpServletRequest request,
Object domainObject) {
// 先用RBAC做快速检查
if (rbacService.hasRoleAccess(auth, "ADMIN")) {
return true;
}
// 需要更细粒度控制时使用ABAC
return abacService.checkAttributes(
auth,
domainObject,
request.getMethod()
);
}
}
选择建议
-
选择 RBAC :
-
权限结构相对简单固定
-
角色数量有限且稳定
-
不需要基于资源属性的访问控制
-
-
选择 ABAC :
-
需要复杂的条件授权(时间、位置等)
-
权限规则频繁变化
-
需要基于资源属性的访问控制
-
系统需要高度灵活的权限管理
-
Spring Security 原生更偏向 RBAC 模型,但通过自定义 PermissionEvaluator
和策略引擎可以实现 ABAC 功能。
Spring Security认证授权流程图概述
如何在 request 之间共享 SecurityContext?
既然SecurityContext 是存放在 ThreadLocal 中的,而且在每次权限鉴定的时候,都是从 ThreadLocal 中获取 SecurityContext 中保存的 Authentication。那么既然不同的 request 属于不同的线程,为什么每次都可以从 ThreadLocal 中获取到当前用户对应的 SecurityContext 呢?
- 在 Web 应用中这是通过 SecurityContextPersistentFilter 实现的,默认情况下其在每次请求开始的时候,都会从 session 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。
- 在请求结束后又会将 SecurityContextHolder 所持有的 SecurityContext 保存在 session 中,并且清除 SecurityContextHolder 所持有的 SecurityContext。
- 这样当我们第一次访问系统的时候,SecurityContextHolder 所持有的 SecurityContext 肯定是空的。待我们登录成功后,SecurityContextHolder 所持有的 SecurityContext 就不是空的了,且包含有认证成功的 Authentication 对象。
- 待请求结束后我们就会将 SecurityContext 存在 session 中,等到下次请求的时候就可以从 session 中获取到该 SecurityContext 并把它赋予给 SecurityContextHolder 了。
- 由于 SecurityContextHolder 已经持有认证过的 Authentication 对象了,所以下次访问的时候也就不再需要进行登录认证了。