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

    • 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
    • 最佳实践

AuthenticationManager

定义与作用

AuthenticationManager 是 Spring Security 认证体系的顶层入口接口,只定义了一个方法:

public interface AuthenticationManager {
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

所有认证请求——无论是表单登录、HTTP Basic、JWT 还是 OAuth2——最终都必须汇聚到 AuthenticationManager.authenticate() 才能被框架认可。它是认证流程的统一网关。

手写过滤器的痛点

手写登录时,认证逻辑通常散落在各个 Servlet 或 Controller 中:

// 痛点:认证逻辑分散,每个入口各写一套
@PostMapping("/api/login")
public ResponseEntity<?> apiLogin(@RequestBody LoginDto dto) {
    // 自己查数据库、自己比对密码、自己组装 Session
}

@PostMapping("/web/login")
public String webLogin(@RequestParam String username, @RequestParam String password) {
    // 又写一套几乎相同的逻辑
}

// 痛点:没有统一接口,无法做到"换数据源只改一处"
// 痛点:认证成功/失败后的处理逻辑无法复用

AuthenticationManager 通过单一接口聚合所有认证入口,让任何过滤器、任何控制器、任何外部系统都可以调用同一套认证能力。


核心原理

接口极简,职责单一

// 输入:一个未认证的 Authentication Token(通常只有用户名和密码)
// 输出:一个已认证的 Authentication(包含用户主体、权限列表、已认证标记)
// 异常:认证失败时抛出 AuthenticationException 子类
Authentication result = authenticationManager.authenticate(unauthenticatedToken);

类图与实现关系

AuthenticationManager 本身不处理任何具体认证逻辑,真正干活的是它的实现类 ProviderManager。ProviderManager 内部维护一个 AuthenticationProvider 列表,将认证请求委托给链中的某个 Provider 执行。这是一种典型的责任链 + 策略混合模式。

为什么设计成接口?

Spring Security 需要支持多种认证方式共存:表单登录、HTTP Basic、OAuth2、JWT、LDAP 等。每种方式的认证数据来源和校验规则不同,但都需要一个统一的调用入口。AuthenticationManager 作为接口,使得上层过滤器无需关心底层具体实现。

// UsernamePasswordAuthenticationFilter 内部
this.getAuthenticationManager().authenticate(authRequest);

// BasicAuthenticationFilter 内部
this.getAuthenticationManager().authenticate(authRequest);

// 两者调用的是同一个 AuthenticationManager,但背后的 Provider 可能不同

示例一:获取并使用 AuthenticationManager Bean

场景说明

在自定义 Controller 中手动触发认证(如双因素认证的第二步),需要直接调用 AuthenticationManager。

操作前配置

默认配置下,AuthenticationManager 由 Spring Boot 自动构建,但并未暴露为 Bean,外部无法直接注入:

// 错误尝试:直接注入会失败或拿到不期望的实例
@Autowired
private AuthenticationManager authenticationManager;  // 可能报错或拿到null

操作后配置

显式将 AuthenticationManager 暴露为 Bean:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("alice").password("{noop}pass").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authorize -> authorize
                .antMatchers("/mfa/**").authenticated()
                .anyRequest().permitAll()
            )
            .formLogin(Customizer.withDefaults());
    }
}

自定义 Controller 中调用:

@RestController
@RequestMapping("/mfa")
public class MfaController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @PostMapping("/verify")
    public ResponseEntity<?> verify(@RequestParam String code) {
        // 第一步认证已在 Session 中,此处做第二步验证
        // 实际场景:从 Session 取出临时 Token,再验证 OTP
        UsernamePasswordAuthenticationToken token =
            new UsernamePasswordAuthenticationToken("alice", code);
        try {
            Authentication result = authenticationManager.authenticate(token);
            SecurityContextHolder.getContext().setAuthentication(result);
            return ResponseEntity.ok("双因素认证通过");
        } catch (BadCredentialsException e) {
            return ResponseEntity.status(401).body("验证码错误");
        }
    }
}

结果分析

  • 通过 authenticationManagerBean() 暴露后,任何 Spring Bean 都可注入并调用认证能力
  • 自定义认证流程(如短信验证码、图形验证码、双因素认证)可以复用框架的 Provider 链和异常体系

示例二:自定义 AuthenticationManager 实现隔离认证域

场景说明

系统中存在两套用户体系:普通用户(User)和管理后台用户(Admin),需要完全隔离的认证数据源和密码策略。

操作前配置

两套用户共用同一个 AuthenticationManager,导致代码中需要大量判断用户类型,耦合严重:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(mixedUserDetailsService);  // 一个Service里判断是User还是Admin
}

操作后配置

为两个域分别创建独立的 ProviderManager,通过 parent 关系实现层级委托:

@Configuration
@EnableWebSecurity
public class MultiRealmSecurityConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();  // 普通用户数据源
    }

    @Bean
    public UserDetailsService adminDetailsService() {
        return new AdminDetailsServiceImpl();  // 管理员数据源
    }

    @Bean
    public PasswordEncoder userPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public PasswordEncoder adminPasswordEncoder() {
        return new BCryptPasswordEncoder(12);  // 管理员使用更高强度
    }

    // 普通用户认证管理器
    @Bean
    public AuthenticationManager userAuthenticationManager() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService());
        provider.setPasswordEncoder(userPasswordEncoder());
        return new ProviderManager(Collections.singletonList(provider));
    }

    // 管理员认证管理器
    @Bean
    public AuthenticationManager adminAuthenticationManager() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(adminDetailsService());
        provider.setPasswordEncoder(adminPasswordEncoder());
        return new ProviderManager(Collections.singletonList(provider));
    }

    // 过滤器链:普通用户
    @Bean
    @Order(1)
    public SecurityFilterChain userChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/user/**")
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin(Customizer.withDefaults())
            .authenticationManager(userAuthenticationManager());
        return http.build();
    }

    // 过滤器链:管理员
    @Bean
    @Order(2)
    public SecurityFilterChain adminChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/admin/**")
            .authorizeHttpRequests(auth -> auth.anyRequest().hasRole("ADMIN"))
            .httpBasic(Customizer.withDefaults())
            .authenticationManager(adminAuthenticationManager());
        return http.build();
    }
}

结果分析

  • /user/** 的表单登录走 userAuthenticationManager,查普通用户表,使用默认强度 BCrypt
  • /admin/** 的 Basic 认证走 adminAuthenticationManager,查管理员表,使用强度 12 的 BCrypt
  • 两套认证域完全隔离,代码清晰,互不干扰

易错场景:忘记暴露 Bean 导致注入失败

问题描述

在 Spring Security 5.x 中,如果不显式调用 authenticationManagerBean(),直接在其他 Bean 中注入 AuthenticationManager 会报错或拿到 null。

错误代码

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 没有重写 authenticationManagerBean() 方法

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("test").password("{noop}test").roles("USER");
    }
}

@Service
public class SomeService {
    @Autowired
    private AuthenticationManager authenticationManager;  // 启动时报错:找不到类型为 AuthenticationManager 的 Bean
}

正确做法

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

面试考点:AuthenticationManager 为什么通常不直接暴露为 Bean?Spring Security 的 WebSecurityConfigurerAdapter 是如何构建内部 AuthenticationManager 的?

上一页
认证流程
下一页
ProviderManager