乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

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);
}

三个参数的含义:

参数类型说明
authenticationAuthentication当前用户的安全上下文(身份 + 权限)
objectObject被访问的资源(如 FilterInvocation 封装了 HTTP 请求)
configAttributesCollection<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)

场景说明

金融系统后台管理接口,要求同时满足:

  1. 拥有 ADMIN 角色(RoleVoter 检查)
  2. 已通过完整登录认证(非 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.xSpring Security 6.x+
默认决策器AccessDecisionManager + Voter 机制AuthorizationManager 接口
URL 授权过滤器FilterSecurityInterceptorAuthorizationFilter
配置方式HttpSecurity.authorizeRequests() + accessDecisionManager()HttpSecurity.authorizeHttpRequests()
新方法基于投票AuthorizationDecision 直接返回 GRANT/DENY

注意:本教程基于 Spring Security 5.x,使用 AccessDecisionManager + FilterSecurityInterceptor 体系。在 6.x 中,AuthorizationManager 已替代 AccessDecisionManager,但 5.x 的投票机制仍是理解 Spring Security 授权原理的重要基础。

上一页
GrantedAuthority
下一页
AccessDecisionVoter