SpringSecurity源码解析(四) 认证器创建流程:从 AuthenticationConfiguration 到 ProviderManager

AuthenticationManager 是 Spring Security 认证体系的核心引擎。本文深入拆解它的创建流程:AuthenticationConfiguration 如何自动加载全局配置器,InitializeUserDetailsBeanManagerConfigurer 如何自动创建 DaoAuthenticationProvider,以及 ProviderManager 的委托认证机制。

前言

在前面三篇文章中,我们理清了 Spring Security 的过滤器链是如何构建和执行的。但有一个关键组件一直在"幕后"------AuthenticationManager(认证管理器)。每次表单登录、每次 HTTP Basic 认证,最终都会走到 AuthenticationManager。

本文将回答三个问题:

  1. AuthenticationManager 是如何被创建的?
  2. DaoAuthenticationProvider 为什么能"自动"出现?
  3. Spring Boot 的 UserDetailsServiceAutoConfiguration 在什么条件下才会创建内存用户?

一、AuthenticationConfiguration:全局认证配置中心

1.1 角色定位

AuthenticationConfiguration 是 Spring Security 自动配置的"认证中枢",它负责:

  1. 创建全局 AuthenticationManager
  2. 加载全局认证配置器(GlobalAuthenticationConfigurerAdapter
  3. 管理认证相关的 Bean 依赖(PasswordEncoderAuthenticationEventPublisher

1.2 核心源码

java 复制代码
@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {

    private AtomicBoolean buildingAuthenticationManager = new AtomicBoolean();
    private ApplicationContext applicationContext;
    private AuthenticationManager authenticationManager;
    private boolean authenticationManagerInitialized;
    private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers =
        Collections.emptyList();
    private ObjectPostProcessor<Object> objectPostProcessor;

    @Bean
    public AuthenticationManagerBuilder authenticationManagerBuilder(
            ObjectPostProcessor<Object> objectPostProcessor,
            ApplicationContext context) {
        LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
        AuthenticationEventPublisher eventPublisher =
            getAuthenticationEventPublisher(context);
        DefaultPasswordEncoderAuthenticationManagerBuilder result =
            new DefaultPasswordEncoderAuthenticationManagerBuilder(
                objectPostProcessor, defaultPasswordEncoder);
        if (eventPublisher != null) {
            result.authenticationEventPublisher(eventPublisher);
        }
        return result;
    }

    //  三个 static @Bean 全局配置器
    @Bean
    public static GlobalAuthenticationConfigurerAdapter
            enableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) {
        return new EnableGlobalAuthenticationAutowiredConfigurer(context);
    }

    @Bean
    public static InitializeUserDetailsBeanManagerConfigurer
            initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
        return new InitializeUserDetailsBeanManagerConfigurer(context);
    }

    @Bean
    public static InitializeAuthenticationProviderBeanManagerConfigurer
            initializeAuthenticationProviderBeanManagerConfigurer(
                ApplicationContext context) {
        return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
    }

    //  收集所有 GlobalAuthenticationConfigurerAdapter 类型的 Bean
    @Autowired(required = false)
    public void setGlobalAuthenticationConfigurers(
            List<GlobalAuthenticationConfigurerAdapter> configurers) {
        configurers.sort(AnnotationAwareOrderComparator.INSTANCE);
        this.globalAuthConfigurers = configurers;
    }
}

1.3 getAuthenticationManager 方法

java 复制代码
public AuthenticationManager getAuthenticationManager() throws Exception {
    if (this.authenticationManagerInitialized) {
        return this.authenticationManager;       // 已构建,直接返回
    }
    // 从容器获取 AuthenticationManagerBuilder
    AuthenticationManagerBuilder authBuilder =
        this.applicationContext.getBean(AuthenticationManagerBuilder.class);
    // 重入检测:防止循环依赖
    if (this.buildingAuthenticationManager.getAndSet(true)) {
        return new AuthenticationManagerDelegator(authBuilder);  // 返回延迟代理
    }
    //  遍历所有全局配置器,依次 apply 到 builder
    for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
        authBuilder.apply(config);
    }
    //  触发 build -> doBuild() -> init -> configure -> performBuild
    this.authenticationManager = authBuilder.build();
    if (this.authenticationManager == null) {
        this.authenticationManager = getAuthenticationManagerBean();  // 兜底
    }
    this.authenticationManagerInitialized = true;
    return this.authenticationManager;
}

关键点

  1. 全局 AuthenticationManager延迟初始化的,只有在第一次被请求时才会构建
  2. 构建过程:收集所有 GlobalAuthenticationConfigurerAdapter Bean -> 按 @Order 排序 -> 依次 apply 到 builder -> build()
  3. 内建重入防护 :通过 AtomicBoolean 检测循环依赖,如检测到则返回 AuthenticationManagerDelegator 代理对象(延迟到首次 authenticate() 调用时才真正完成构建)
  4. 兜底机制 :如果 build() 返回 null(既无 provider 也无 parent),则通过 lazyBean() 延迟查找容器中唯一的 AuthenticationManager Bean

1.4 DefaultPasswordEncoderAuthenticationManagerBuilder

AuthenticationConfiguration 内部定义了一个重要的子类,继承自 AuthenticationManagerBuilder

java 复制代码
static class DefaultPasswordEncoderAuthenticationManagerBuilder
        extends AuthenticationManagerBuilder {

    private PasswordEncoder defaultPasswordEncoder;

    @Override
    public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>
            inMemoryAuthentication() throws Exception {
        return super.inMemoryAuthentication()
            .passwordEncoder(this.defaultPasswordEncoder);
    }

    @Override
    public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder>
            jdbcAuthentication() throws Exception {
        return super.jdbcAuthentication()
            .passwordEncoder(this.defaultPasswordEncoder);
    }

    @Override
    public <T extends UserDetailsService>
            DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T>
            userDetailsService(T userDetailsService) throws Exception {
        return super.userDetailsService(userDetailsService)
            .passwordEncoder(this.defaultPasswordEncoder);
    }
}

该子类覆盖了所有需要 PasswordEncoder 的 DSL 方法,自动注入 LazyPasswordEncoder 作为默认编码器。而 LazyPasswordEncoder 在首次使用时才会从容器中查找唯一的 PasswordEncoder Bean(若无则使用 DelegatingPasswordEncoder 兜底)。


二、自动加载的全局配置器及其执行顺序

2.1 三个关键配置器

AuthenticationConfiguration 通过 static @Bean 声明了三个全局配置器,它们通过 @Autowired setGlobalAuthenticationConfigurers 被自动收集并@Order 排序后依次执行

配置器 @Order 执行顺序 作用
EnableGlobalAuthenticationAutowiredConfigurer 100(继承自父类 GlobalAuthenticationConfigurerAdapter 1 最先 通过 getBeansWithAnnotation(@EnableGlobalAuthentication) 提前初始化标注了该注解的 Bean
InitializeAuthenticationProviderBeanManagerConfigurer LOWEST_PRECEDENCE - 5100 2 其次 查找容器中唯一的 AuthenticationProvider Bean -> 直接注册
InitializeUserDetailsBeanManagerConfigurer LOWEST_PRECEDENCE - 5000 3 最后 查找容器中唯一的 UserDetailsService Bean -> 自动创建 DaoAuthenticationProvider

@Order 来源说明

  • EnableGlobalAuthenticationAutowiredConfigurer 没有显式标注 @Order,但继承自 GlobalAuthenticationConfigurerAdapter,后者在类上标注了 @Order(100)。Spring 的 AnnotationAwareOrderComparator 会沿类继承链查找 @Order,因此它能获得 100 的排序值。
  • InitializeUserDetailsBeanManagerConfigurer 自身标注 @Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER),其中 DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000
  • InitializeAuthenticationProviderBeanManagerConfigurer 自身标注 @Order(InitializeAuthenticationProviderBeanManagerConfigurer.DEFAULT_ORDER),其中 DEFAULT_ORDER = InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER - 100,即 LOWEST_PRECEDENCE - 5100注意 :这个值比 InitializeUserDetailsBeanManagerConfigurer 的 order 小 100,所以它执行(order 值越小优先级越高)。

2.2 configure 方法的调用时机

调用链路:getAuthenticationManager() -> authBuilder.apply(config) -> authBuilder.build() -> doBuild()doBuild()AbstractConfiguredSecurityBuilder 中定义:

java 复制代码
protected final O doBuild() throws Exception {
    synchronized (this.configurers) {
        this.buildState = BuildState.INITIALIZING;
        beforeInit();
        init();      // 1 init 阶段:调用所有 Configurer 的 init()
                     //    -> 外部类 init() 内部 new 出内部 Configurer 并 apply
                     //    -> 新 apply 的内部 Configurer 会在 while 循环中被再次 init()

        this.buildState = BuildState.CONFIGURING;
        beforeConfigure();
        configure(); // 2 configure 阶段:调用所有 Configurer 的 configure()
                     //    -> 内部类查找 Bean、创建 Provider、注册到 builder

        this.buildState = BuildState.BUILDING;
        O result = performBuild();  // 3 build 阶段:构建最终对象(ProviderManager)
        this.buildState = BuildState.BUILT;
        return result;
    }
}

两阶段设计的关键代码 --init() 方法:

java 复制代码
private void init() throws Exception {
    Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    for (SecurityConfigurer<O, B> configurer : configurers) {
        configurer.init((B) this);
    }
    // while 循环:处理在 init() 过程中新 apply 进来的 Configurer
    while (!this.configurersAddedInInitializing.isEmpty()) {
        List<SecurityConfigurer<O, B>> toInit = this.configurersAddedInInitializing;
        this.configurersAddedInInitializing = new ArrayList<>();
        for (SecurityConfigurer<O, B> configurer : toInit) {
            configurer.init((B) this);
        }
    }
}

理解这个时序至关重要 :外部类的 init() 负责注册内部类(如 InitializeUserDetailsBeanManagerConfigurer.init()auth.apply(new InitializeUserDetailsManagerConfigurer())),内部类的 configure() 负责真正的 Provider 创建逻辑。这种两阶段设计保证了所有 Configurer 都在 init 阶段完成注册,然后在 configure 阶段统一执行创建逻辑。

2.3 构建流程流程图

2.4 InitializeUserDetailsBeanManagerConfigurer 源码

java 复制代码
@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER) // LOWEST_PRECEDENCE - 5000
class InitializeUserDetailsBeanManagerConfigurer
        extends GlobalAuthenticationConfigurerAdapter {

    static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;

    private final ApplicationContext context;

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.apply(new InitializeUserDetailsManagerConfigurer());  // 注册内部类
    }

    class InitializeUserDetailsManagerConfigurer
            extends GlobalAuthenticationConfigurerAdapter {

        private final Log logger = LogFactory.getLog(getClass());

        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            String[] beanNames =
                InitializeUserDetailsBeanManagerConfigurer.this.context
                    .getBeanNamesForType(UserDetailsService.class);

            // 0. 守卫:优先检查 auth.isConfigured(),而非 beanNames
            if (auth.isConfigured()) {
                if (beanNames.length > 0) {
                    this.logger.warn(
                        "Global AuthenticationManager configured with an "
                        + "AuthenticationProvider bean. UserDetailsService beans "
                        + "will not be used ...");
                }
                return;
            }

            // 1. 查找 UserDetailsService Bean(要求恰好一个)
            if (beanNames.length == 0) { return; }
            if (beanNames.length > 1) {
                this.logger.warn("Found " + beanNames.length
                    + " UserDetailsService beans ...");
                return;
            }
            UserDetailsService userDetailsService =
                context.getBean(beanNames[0], UserDetailsService.class);

            // 2. 查找可选依赖
            PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
            UserDetailsPasswordService passwordManager =
                getBeanOrNull(UserDetailsPasswordService.class);
            CompromisedPasswordChecker passwordChecker =
                getBeanOrNull(CompromisedPasswordChecker.class);

            // 3. 创建 DaoAuthenticationProvider(构造器注入 UserDetailsService)
            DaoAuthenticationProvider provider =
                new DaoAuthenticationProvider(userDetailsService);
            if (passwordEncoder != null) {
                provider.setPasswordEncoder(passwordEncoder);
            }
            if (passwordManager != null) {
                provider.setUserDetailsPasswordService(passwordManager);
            }
            if (passwordChecker != null) {
                provider.setCompromisedPasswordChecker(passwordChecker);
            }
            provider.afterPropertiesSet();  // 校验 UserDetailsService 等必填项

            // 4. 注册到 AuthenticationManagerBuilder
            auth.authenticationProvider(provider);
        }

        private <T> T getBeanOrNull(Class<T> type) {
            return context.getBeanProvider(type).getIfUnique();
        }
    }
}

核心逻辑

  1. 先检查 auth.isConfigured() -- 如果已有 Provider 注册则跳过(保护性守卫,并输出警告日志)
  2. 要求容器中恰好一个 UserDetailsService Bean(0 个跳过,>1 个警告跳过)
  3. 构造器注入创建 DaoAuthenticationProvider
  4. 注入可选依赖:PasswordEncoderUserDetailsPasswordServiceCompromisedPasswordChecker
  5. 调用 afterPropertiesSet() 完成校验 -> 注册到 builder

注意 :源码中 isConfigured() 的判断在 beanNames 获取之后 。这意味着即使已有 Provider 注册,只要容器中存在 UserDetailsService Bean,就会输出警告日志,提醒开发者注意潜在冲突。

2.5 InitializeAuthenticationProviderBeanManagerConfigurer

java 复制代码
@Order(InitializeAuthenticationProviderBeanManagerConfigurer.DEFAULT_ORDER)
// LOWEST_PRECEDENCE - 5100
class InitializeAuthenticationProviderBeanManagerConfigurer
        extends GlobalAuthenticationConfigurerAdapter {

    static final int DEFAULT_ORDER =
        InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER - 100;

    private final ApplicationContext context;

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.apply(new InitializeAuthenticationProviderManagerConfigurer());
    }

    class InitializeAuthenticationProviderManagerConfigurer
            extends GlobalAuthenticationConfigurerAdapter {

        @Override
        public void configure(AuthenticationManagerBuilder auth) {
            // 0. 守卫:已被其他配置器处理过则跳过
            if (auth.isConfigured()) { return; }

            // 1. 查找 AuthenticationProvider Bean(要求恰好一个)
            String[] beanNames = context.getBeanNamesForType(
                AuthenticationProvider.class);
            if (beanNames.length == 0) { return; }
            if (beanNames.length > 1) {
                this.logger.info("Found " + beanNames.length
                    + " AuthenticationProvider beans ...");
                return;
            }

            // 2. 直接注册
            AuthenticationProvider authenticationProvider =
                context.getBean(beanNames[0], AuthenticationProvider.class);
            auth.authenticationProvider(authenticationProvider);
        }
    }
}

与上一个配置器的区别

维度 InitializeUserDetailsBeanManagerConfigurer InitializeAuthenticationProviderBeanManagerConfigurer
查找的 Bean UserDetailsService AuthenticationProvider
创建的 Provider 自动创建 DaoAuthenticationProvider 直接使用已有 Bean
适用场景 标准用户名密码登录 自定义认证逻辑(短信验证码、LDAP 等)
执行顺序 3 最后(@Order LOWEST-5000) 2 其次(@Order LOWEST-5100)

执行顺序的重要性InitAuthProvider 的 order 是 LOWEST-5100,比 InitUserDetailsLOWEST-5000 小 100,因此先执行 。如果它注册了 Provider,后续的 InitUserDetails 会通过 auth.isConfigured() 检测到并自动跳过(同时输出警告日志)。

2.6 AuthenticationManagerBuilder.isConfigured()

java 复制代码
public boolean isConfigured() {
    return !this.authenticationProviders.isEmpty()
        || this.parentAuthenticationManager != null;
}

只要 authenticationProviders 列表非空,或设置了 parentAuthenticationManager,builder 即视为"已配置"。performBuild() 也同样依赖此方法判断是否返回 null。


三、ProviderManager:认证管理器的核心实现

3.1 类结构

java 复制代码
public class ProviderManager implements AuthenticationManager,
        MessageSourceAware, InitializingBean {

    private List<AuthenticationProvider> providers = Collections.emptyList();
    private AuthenticationManager parent;
    private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
    private boolean eraseCredentialsAfterAuthentication = true;
}

3.2 构造与创建(performBuild)

ProviderManagerAuthenticationManagerBuilder.performBuild() 创建:

java 复制代码
@Override
protected ProviderManager performBuild() throws Exception {
    if (!isConfigured()) {
        this.logger.debug("No authenticationProviders and no "
            + "parentAuthenticationManager defined. Returning null.");
        return null;
    }
    ProviderManager providerManager = new ProviderManager(
        this.authenticationProviders, this.parentAuthenticationManager);
    if (this.eraseCredentials != null) {
        providerManager.setEraseCredentialsAfterAuthentication(
            this.eraseCredentials);
    }
    if (this.eventPublisher != null) {
        providerManager.setAuthenticationEventPublisher(this.eventPublisher);
    }
    providerManager = postProcess(providerManager);
    return providerManager;
}

3.3 authenticate 方法

java 复制代码
@Override
public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {

    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    int currentPosition = 0;
    int size = this.providers.size();

    // 1. 遍历所有 AuthenticationProvider
    for (AuthenticationProvider provider : getProviders()) {
        if (!provider.supports(toTest)) {
            continue;                           // 不支持此类型,跳过
        }
        if (logger.isTraceEnabled()) {
            logger.trace(LogMessage.format(
                "Authenticating request with %s (%d/%d)",
                provider.getClass().getSimpleName(),
                ++currentPosition, size));
        }
        try {
            result = provider.authenticate(authentication);
            if (result != null) {
                copyDetails(authentication, result);
                break;                           // 认证成功,跳出循环
            }
        } catch (AccountStatusException ex) {
            // 账户状态异常:发布失败事件后立即终止
            prepareException(ex, authentication);
            throw ex;
        } catch (InternalAuthenticationServiceException ex) {
            // 内部服务异常:发布失败事件后立即终止
            prepareException(ex, authentication);
            throw ex;
        } catch (AuthenticationException ex) {
            // 普通认证失败(如 BadCredentialsException):记录后继续尝试下一个
            ex.setAuthenticationRequest(authentication);
            lastException = ex;
        }
    }

    // 2. 所有 Provider 都无法认证,委托给父级
    if (result == null && this.parent != null) {
        try {
            parentResult = this.parent.authenticate(authentication);
            result = parentResult;
        } catch (ProviderNotFoundException ex) {
            // 父级也找不到,忽略(后续统一抛异常)
        } catch (AuthenticationException ex) {
            parentException = ex;
            lastException = ex;
        }
    }

    // 3. 认证成功:清除凭据 -> 发布事件 -> 返回
    if (result != null) {
        if (this.eraseCredentialsAfterAuthentication
                && (result instanceof CredentialsContainer)) {
            ((CredentialsContainer) result).eraseCredentials();
        }
        // 仅在非父级认证成功时发布事件(防止重复)
        if (parentResult == null) {
            this.eventPublisher.publishAuthenticationSuccess(result);
        }
        return result;
    }

    // 4. 认证失败
    if (lastException == null) {
        lastException = new ProviderNotFoundException(
            this.messages.getMessage("ProviderManager.providerNotFound",
                new Object[] { toTest.getName() },
                "No AuthenticationProvider found for {0}"));
    }
    // 仅在非父级抛出异常时发布失败事件(防止重复)
    if (parentException == null) {
        prepareException(lastException, authentication);
    }
    throw lastException;
}

// 辅助方法:发布失败事件 + 设置 authentication 到异常中
private void prepareException(AuthenticationException ex,
        Authentication auth) {
    ex.setAuthenticationRequest(auth);
    this.eventPublisher.publishAuthenticationFailure(ex, auth);
}

3.4 异常处理策略

ProviderManagerAuthenticationException 的子类采用了不同的处理策略:

异常类型 处理策略 原因
AccountStatusException 立即抛出,发布失败事件 账户锁定/禁用是终局判断,换 Provider 也没用
InternalAuthenticationServiceException 立即抛出,发布失败事件 系统内部错误(UserDetailsService 异常等),不应继续
BadCredentialsException 等普通异常 记录下来,继续尝试下一个 Provider 换个 Provider 可能认证成功(如 LDAP -> 数据库)
ProviderNotFoundException(来自父级) 静默忽略 父级没有对应 Provider 是正常的

3.5 copyDetails 方法

java 复制代码
private void copyDetails(Authentication source, Authentication dest) {
    if ((dest instanceof AbstractAuthenticationToken token)
            && (dest.getDetails() == null)) {
        token.setDetails(source.getDetails());
    }
}

仅当目标为 AbstractAuthenticationToken 且其 details 为空时才复制,保留目标可能已有的 details。

3.6 认证流程图


四、DaoAuthenticationProvider 的核心逻辑

检索用户与密码校验

java 复制代码
public class DaoAuthenticationProvider
        extends AbstractUserDetailsAuthenticationProvider {

    //  注意:passwordEncoder 是一个 Supplier,通过 SingletonSupplier 包装
    private Supplier<PasswordEncoder> passwordEncoder = SingletonSupplier
        .of(PasswordEncoderFactories::createDelegatingPasswordEncoder);

    private UserDetailsService userDetailsService;
    private UserDetailsPasswordService userDetailsPasswordService;
    private CompromisedPasswordChecker compromisedPasswordChecker;

    @Override
    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {

        prepareTimingAttackProtection();             // 准备时序攻击防护
        try {
            // 1. 调用 UserDetailsService 查询用户
            UserDetails loadedUser = this.getUserDetailsService()
                .loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                    "UserDetailsService returned null, "
                    + "which is an interface contract violation");
            }
            return loadedUser;
        } catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);  // 防时序攻击
            throw ex;
        } catch (InternalAuthenticationServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {

        if (authentication.getCredentials() == null) {
            throw new BadCredentialsException("Bad credentials");  // 无凭据
        }
        // 2. 校验密码(注意 passwordEncoder 是 Supplier,需 .get() 获取实例)
        String presentedPassword = authentication.getCredentials().toString();
        if (!this.passwordEncoder.get().matches(
                presentedPassword, userDetails.getPassword())) {
            throw new BadCredentialsException("Bad credentials");
        }
    }
}

关键细节

  • passwordEncoder 字段类型是 Supplier<PasswordEncoder>(由 SingletonSupplier 包装),因此调用时需使用 passwordEncoder.get().matches() 而非 passwordEncoder.matches()
  • 默认的 PasswordEncoder 通过 PasswordEncoderFactories.createDelegatingPasswordEncoder() 懒加载创建
  • retrieveUser()Exception(非 Security 异常)做了统一包装为 InternalAuthenticationServiceException

五、实战:自定义 UserDetailsService

在大多数生产环境中,用户信息存储在数据库中,需要自定义 UserDetailsService

java 复制代码
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {

        User user = userMapper.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException(
                "User not found: " + username));

        return org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(user.getPassword())
            .authorities(user.getRoles().stream()
                .map(role -> "ROLE_" + role.getName())
                .toArray(String[]::new))
            .build();
    }
}

效果

  1. InitializeUserDetailsBeanManagerConfigurer 检测到 CustomUserDetailsService
  2. 自动创建 DaoAuthenticationProvider,注入 userDetailsService + 容器中的 PasswordEncoder
  3. Spring Boot 的 UserDetailsServiceAutoConfiguration@ConditionalOnMissingBean(UserDetailsService.class) 条件不再满足 -> 不创建内存用户
  4. 容器中最终只有一个 DaoAuthenticationProvider,由 ProviderManager 管理

六、总结

组件 职责 创建条件
AuthenticationConfiguration 全局认证配置中心,声明 3 个全局配置器 + 管理 build 流程 @Configuration,Spring 自动扫描
EnableGlobalAuthenticationAutowiredConfigurer 提前初始化 @EnableGlobalAuthentication 标注的 Bean 始终存在(@Order 100,来自父类,最先执行)
InitializeAuthenticationProviderBeanManagerConfigurer 查找唯一的 AuthenticationProvider Bean -> 直接注册 容器中恰好一个 AuthenticationProvider Bean(@Order LOWEST-5100)
InitializeUserDetailsBeanManagerConfigurer 查找唯一的 UserDetailsService Bean -> 创建 DaoAuthenticationProvider 容器中恰好一个 UserDetailsService Bean(@Order LOWEST-5000)
ProviderManager 遍历 AuthenticationProvider 执行认证,含异常分类处理和父级回退 performBuild() 构建(providers 为空且无 parent 时返回 null)
DaoAuthenticationProvider 使用 UserDetailsService 查询用户 + PasswordEncoder 校验密码 InitializeUserDetailsBeanManagerConfigurer 自动创建

build 构建生命周期


下一篇预告:《Spring Security 表单登录认证全流程:UsernamePasswordAuthenticationFilter 源码拆解》将深入分析从用户提交登录表单到认证成功的完整源码链路。

相关推荐
ServBay21 小时前
别再用初级写法写Rust了,8个写法你值得拥有
后端·rust
jingling5551 天前
go | 环境安装和快速入门
开发语言·后端·golang
Darren2451 天前
流程步骤模板 - @StepStatus 注解方案
后端
小闹5491 天前
Claude Code 给自己接了一部飞书,从此不用守在工位等它
后端·claude
浮游本尊1 天前
Java学习第41天 - 复杂查询、多表关联、索引优化与慢 SQL 调优
后端
llz_1121 天前
web-第五次课后作业
前端·后端·http
雨辰AI1 天前
生产级实测:SpringBoot3 + 达梦数据库接口从 200ms 优化至 20ms 完整调优指南
java·数据库·spring boot·后端·政务
Solis1 天前
Raft:分布式系统的定海神针
后端·架构
程序员老申1 天前
第三篇 5 天 12 个 commit:踩坑实录与代码演进
后端·程序员