bigywater.tech
Spring · 103

认证流程:AuthenticationManager、Provider 与 SecurityContext

用户名不存在和密码错怎么区分不开?禁用的用户用错误密码登录会得到什么异常?认证成功后密码还在吗?异步线程里为什么拿不到当前用户?这篇在 Spring Boot 4.0.8、Spring Security 7 上直接调用 AuthenticationManager 逐个实测。

286 vs 283 ms
密码错与用户不存在的 authenticate 用时:几乎一样,异常也都是 BadCredentialsException
FACTOR_PASSWORD
Spring Security 7 认证成功后 authorities 里多出的一条,记录认证因子
null / alice / erin
普通线程池里看到的用户:默认是 null;INHERITABLETHREADLOCAL 下 erin 的请求里仍是 alice

各种失败

实测 · 本机执行输出
AuthenticationManager = ProviderManager,providers = [DaoAuthenticationProvider]
  authenticate(alice, pw):成功,返回 UsernamePasswordAuthenticationToken;用时 572 ms;发布的事件 [AuthenticationSuccessEvent]
  authenticate(alice, wrong):失败 BadCredentialsException:Bad credentials;用时 286 ms;发布的事件 [AuthenticationFailureBadCredentialsEvent]
  authenticate(nobody, pw):失败 BadCredentialsException:Bad credentials;用时 283 ms;发布的事件 [AuthenticationFailureBadCredentialsEvent]
  authenticate(bob, pw):失败 DisabledException:User is disabled;用时 290 ms;发布的事件 [AuthenticationFailureDisabledEvent]
  authenticate(carol, pw):失败 LockedException:User account is locked;用时 286 ms;发布的事件 [AuthenticationFailureLockedEvent]
  authenticate(dave, pw):失败 CredentialsExpiredException:User credentials have expired;用时 288 ms;发布的事件 [AuthenticationFailureCredentialsExpiredEvent]
  authenticate(bob, wrong):失败 DisabledException:User is disabled;用时 294 ms;发布的事件 [AuthenticationFailureDisabledEvent]

密码错与用户不存在对外一样;禁用、锁定、凭据过期各有异常,且先于密码检查。

成功的返回值

实测 · 本机执行输出
  传入的对象:authenticated=false,credentials=pw;返回的对象:类型=UsernamePasswordAuthenticationToken,authenticated=true,principal 类型=User,credentials=null,authorities=[ROLE_USER, FactorGrantedAuthority [authority=FACTOR_PASSWORD, issuedAt=2026-10-20T04:00:00.894720Z]];返回的与传入的是同一个对象:false

新的 Authentication,credentials 被擦掉,多了 FACTOR_PASSWORD 权限。

隐藏用户不存在

实测 · 本机执行输出
  hideUserNotFoundExceptions=false:authenticate(nobody, pw):失败 UsernameNotFoundException:user not found;用时 576 ms

hideUserNotFoundExceptions=false 会抛 UsernameNotFoundException,暴露用户是否存在。

线程之间

实测 · 本机执行输出
  用 alice 登录 GET /who → 请求线程=alice;普通线程池=null;新建线程=null;DelegatingSecurityContextExecutorService=alice
  用 erin 登录 GET /who → 请求线程=erin;普通线程池=null;新建线程=null;DelegatingSecurityContextExecutorService=erin
  -- 同样的请求,启动时设置 spring.security.strategy=MODE_INHERITABLETHREADLOCAL:
  用 alice 登录 GET /who → 请求线程=alice;普通线程池=alice;新建线程=alice;DelegatingSecurityContextExecutorService=alice
  用 erin 登录 GET /who → 请求线程=erin;普通线程池=alice;新建线程=erin;DelegatingSecurityContextExecutorService=erin

默认 ThreadLocal 不带到其他线程;INHERITABLE 会让线程池里一直是旧用户;用 DelegatingSecurityContextExecutorService。

分步演示

认证流程
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Security 7,BCrypt strength 12)。InMemoryUserDetailsManager 里五个用户(alice、erin 正常,bob 禁用,carol 锁定,dave 凭据过期),密码都是 pw;authenticate 的耗时包含 BCrypt 校验,首次调用较慢;SecurityContext 的线程实验是启动两次应用,第二次加 -Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL;线程池只有 1 个线程
    • 没有验证:自定义 AuthenticationProvider 与多个 provider 的尝试顺序、parent 链;RememberMe、预认证等其他 Authentication 类型;FactorGrantedAuthority 在 Spring Security 7 多因子认证里的具体用法;ReactiveSecurityContextHolder;密码错误次数过多后的锁定(见第 114 篇);hideUserNotFoundExceptions=false 时用户不存在与存在时的耗时差异(只测了异常类型)
    京ICP备2026067502号-1京公网安备11010502063487号