AccessDecisionManager
一句话定义:访问决策管理器,Spring Security 授权体系的核心仲裁者,通过汇总一组
AccessDecisionVoter的投票结果,决定是否允许当前请求访问受保护资源。
定义与作用
AccessDecisionManager 是 Spring Security 5.x 中授权决策的顶层接口。当用户请求到达受保护资源时,过滤器(如 FilterSecurityInterceptor)从 SecurityMetadataSource 获取该资源所需的权限配置(ConfigAttribute 集合),然后调用 AccessDecisionManager.decide() 方法做出最终裁决。
public interface AccessDecisionManager {
// 核心决策方法
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
三个参数的含义:
| 参数 | 类型 | 说明 |
|---|---|---|
authentication | Authentication | 当前用户的安全上下文(身份 + 权限) |
object | Object | 被访问的资源(如 FilterInvocation 封装了 HTTP 请求) |
configAttributes | Collection<ConfigAttribute> | 该资源所需的权限配置 |
核心原理
三种决策策略
Spring Security 提供 AccessDecisionManager 的三种实现,对应不同的投票汇总策略:
| 实现类 | 策略 | 默认 | 适用场景 |
|---|---|---|---|
AffirmativeBased | 一票通过(任一 Voter 投 GRANT 即通过) | ✅ 默认 | 大多数场景,允许不同 Voter 处理不同维度 |
ConsensusBased | 多数表决(GRANT 数 > DENY 数) | ❌ | 多维度权限且需折中 |
UnanimousBased | 全票通过(不允许任何 DENY) | ❌ | 高安全场景,零容忍 |
决策流程
核心逻辑(AffirmativeBased 简化版):
public class AffirmativeBased extends AbstractAccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
if (result == AccessDecisionVoter.ACCESS_GRANTED) {
return; // 一票通过,直接放行
}
if (result == AccessDecisionVoter.ACCESS_DENIED) {
deny++; // 记录反对票
}
}
if (deny > 0) {
throw new AccessDeniedException("Access is denied");
}
// 全部弃权 → 检查 allowIfAllAbstainDecisions
}
}
示例一:自定义决策策略(UnanimousBased)
场景说明
金融系统后台管理接口,要求同时满足:
- 拥有
ADMIN角色(RoleVoter 检查) - 已通过完整登录认证(非 RememberMe,AuthenticatedVoter 检查)
两个条件必须同时满足才能访问。
操作前配置
@Configuration
@EnableWebSecurity
public class BankSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.antMatchers("/bank/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults());
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}admin").roles("ADMIN")
.and()
.withUser("user").password("{noop}user").roles("USER");
}
// 关键:注入自定义的 AccessDecisionManager
@Override
public void configure(WebSecurity web) {
// 默认 AffirmativeBased,这里需要替换
}
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> voters = Arrays.asList(
new RoleVoter(),
new AuthenticatedVoter()
);
// 使用全票通过制:所有 Voter 都不能投 DENY
return new UnanimousBased(voters);
}
}
操作后配置
@Configuration
public class AccessDecisionConfig {
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> voters = Arrays.asList(
new WebExpressionVoter(), // 支持 SpEL 表达式
new RoleVoter(),
new AuthenticatedVoter()
);
return new UnanimousBased(voters);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AccessDecisionManager accessDecisionManager;
public SecurityConfig(AccessDecisionManager accessDecisionManager) {
this.accessDecisionManager = accessDecisionManager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.accessDecisionManager(accessDecisionManager) // 显式指定
.antMatchers("/bank/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults());
}
}
结果分析
使用 UnanimousBased 后:
- 用户通过
RememberMe自动登录(非完整登录)访问/bank/admin/transfer→AuthenticatedVoter投ACCESS_DENIED→ 请求被拒绝 - 普通
USER角色完整登录访问/bank/admin/transfer→RoleVoter投ACCESS_DENIED→ 请求被拒绝 ADMIN角色且通过表单完整登录 → 两个 Voter 都投ACCESS_GRANTED→ 请求通过
示例二:自定义 AccessDecisionManager 实现日志审计
场景说明
企业系统要求记录每次授权决策的详细投票结果,用于安全审计。
操作前配置
public class AuditingAccessDecisionManager implements AccessDecisionManager {
private final AccessDecisionManager delegate;
private static final Logger logger = LoggerFactory.getLogger("SECURITY_AUDIT");
public AuditingAccessDecisionManager(AccessDecisionManager delegate) {
this.delegate = delegate;
}
@Override
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) {
String user = authentication.getName();
String resource = object.toString();
String required = configAttributes.toString();
try {
delegate.decide(authentication, object, configAttributes);
logger.info("[GRANTED] user={}, resource={}, required={}", user, resource, required);
} catch (AccessDeniedException e) {
logger.warn("[DENIED] user={}, resource={}, required={}", user, resource, required);
throw e;
}
}
@Override
public boolean supports(ConfigAttribute attribute) {
return delegate.supports(attribute);
}
@Override
public boolean supports(Class<?> clazz) {
return delegate.supports(clazz);
}
}
@Configuration
public class AuditConfig {
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> voters = Arrays.asList(
new RoleVoter(),
new AuthenticatedVoter(),
new WebExpressionVoter()
);
// 包装一层审计代理
return new AuditingAccessDecisionManager(
new AffirmativeBased(voters)
);
}
}
操作后配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AccessDecisionManager accessDecisionManager;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.accessDecisionManager(accessDecisionManager)
.antMatchers("/audit/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults());
}
}
结果分析
审计日志输出:
[INFO] [GRANTED] user=admin, resource=FilterInvocation: URL: /audit/logs, required=[ROLE_ADMIN]
[WARN] [DENIED] user=user, resource=FilterInvocation: URL: /audit/logs, required=[ROLE_ADMIN]
易错场景:AffirmativeBased 的"一票通过"导致权限绕过
场景:使用 AffirmativeBased(默认)时,配置了两个 Voter:
RoleVoter:检查ROLE_ADMIN- 自定义
IpVoter:检查请求 IP 是否在白名单
开发者期望"同时满足角色和 IP 条件"。
问题:AffirmativeBased 只要有一个 Voter 投 ACCESS_GRANTED 就通过。如果 RoleVoter 投 GRANTED,IpVoter 投 DENY,结果仍然是 GRANTED(因为第一个 GRANTED 立即 return)。
正确做法:需要"同时满足"时,应使用 UnanimousBased。
// 错误:使用默认 AffirmativeBased 期望"同时满足"
@Bean
public AccessDecisionManager accessDecisionManager() {
return new AffirmativeBased(Arrays.asList(
new RoleVoter(),
new IpVoter() // 白名单检查
));
}
// 正确:需要全票通过
@Bean
public AccessDecisionManager accessDecisionManager() {
return new UnanimousBased(Arrays.asList(
new RoleVoter(),
new IpVoter()
));
}
面试考点:
问:
AffirmativeBased和UnanimousBased的区别是什么?在什么场景下必须用UnanimousBased? 答:AffirmativeBased是"一票通过"——只要有任一 Voter 投GRANTED且无 Voter 投DENY(注意:如果有一个 GRANTED 直接 return,后面的 DENY 可能被忽略),请求即通过。UnanimousBased是"全票通过"——只要有任一 Voter 投DENY就拒绝。当需要多个 Voter 的条件同时满足时(如角色 + IP 白名单 + 双因素认证),必须使用UnanimousBased。
5.x vs 6.x 的演进
| 特性 | Spring Security 5.x | Spring Security 6.x+ |
|---|---|---|
| 默认决策器 | AccessDecisionManager + Voter 机制 | AuthorizationManager 接口 |
| URL 授权过滤器 | FilterSecurityInterceptor | AuthorizationFilter |
| 配置方式 | HttpSecurity.authorizeRequests() + accessDecisionManager() | HttpSecurity.authorizeHttpRequests() |
| 新方法 | 基于投票 | AuthorizationDecision 直接返回 GRANT/DENY |
注意:本教程基于 Spring Security 5.x,使用
AccessDecisionManager+FilterSecurityInterceptor体系。在 6.x 中,AuthorizationManager已替代AccessDecisionManager,但 5.x 的投票机制仍是理解 Spring Security 授权原理的重要基础。