上篇文章深入理解Spring Security配置与构建(源码篇)已经介绍了SecurityBuilder以及它的一个重要实现HttpSecurity,在SecurityBuilder的实现类里面,还有一个重要的分支,那就是AuthenicationManagerBuilder,看名字就知道是用来构建AuthenticationManager的,那么就来看看AuthenticationManager是怎么被一步一步构建的。

1、AuthenticationManager

  1. public interface AuthenticationManager {
  2. Authentication authenticate(Authentication authentication) throws AuthenticationException;
  3. }

捋一遍登录Spring Security登录流程(源码篇)一文中,用户进行登录认证的时候,用来处理身份认证的类就是AuthenticationManager,我们也称之为认证管理器。
AuthenticationManager中规范了 Spring Security 中的过滤器要如何执行身份认证,并在身份认证成功后返回一个经过认证的Authentication对象。AuthenticationManager是一个接口,我们可以自定义它的实现,但是通常我们使用更多的是系统提供的ProviderManager

2、ProviderManager

ProviderManager是最常用的AuthenticationManager实现类。
ProviderManager管理了一个AuthenticationProvider集合,每个AuthenticationProvider都是一个认证器,不同的AuthenticationProvider用来处理不同的Authentication对象的认证。一次完整的认证可能会经过多个AuthenticationProvider
深入理解AuthenticationManagerBuilder(源码篇) - 图1

3、AuthenticationProvider

AuthenticationProvider定义了 Spring Security 中的验证逻辑:

  1. public interface AuthenticationProvider {
  2. Authentication authenticate(Authentication authentication)
  3. throws AuthenticationException;
  4. boolean supports(Class<?> authentication);
  5. }

可以看到,就两个方法:

  • authenticate方法用来验证用户身份
  • supports方法用来判断当前的AuthenticationProvider是否支持需要的认证的Authentication

最常用的AuthenticationProvider实现类就是DaoAuthenticationProvider

4、parent

每一个ProviderManager管理多个AuthenticationProvider,同时每一个ProviderManager都可以配置一个parent,如果当前的ProviderManager中认证失败了,还可以去它的parent中继续进行认证,所谓的parent实例,一般也是ProviderManager,即ProviderManagerparent还是ProviderManager
深入理解AuthenticationManagerBuilder(源码篇) - 图2

5、源码分析

先来看看AuthenicationManagerBuilder的一个继承关系类图:
深入理解AuthenticationManagerBuilder(源码篇) - 图3
可以看到,上篇文章中介绍的全是AuthenicationManagerBuilder的父类,所以AuthenicationManagerBuilder已经自动具备了其父类的功能。

  1. public class AuthenticationManagerBuilder
  2. extends
  3. AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
  4. implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
  5. public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
  6. super(objectPostProcessor, true);
  7. }
  8. public AuthenticationManagerBuilder parentAuthenticationManager(
  9. AuthenticationManager authenticationManager) {
  10. if (authenticationManager instanceof ProviderManager) {
  11. eraseCredentials(((ProviderManager) authenticationManager)
  12. .isEraseCredentialsAfterAuthentication());
  13. }
  14. this.parentAuthenticationManager = authenticationManager;
  15. return this;
  16. }
  17. public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
  18. throws Exception {
  19. return apply(new InMemoryUserDetailsManagerConfigurer<>());
  20. }
  21. public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
  22. throws Exception {
  23. return apply(new JdbcUserDetailsManagerConfigurer<>());
  24. }
  25. public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
  26. T userDetailsService) throws Exception {
  27. this.defaultUserDetailsService = userDetailsService;
  28. return apply(new DaoAuthenticationConfigurer<>(
  29. userDetailsService));
  30. }
  31. @Override
  32. protected ProviderManager performBuild() throws Exception {
  33. if (!isConfigured()) {
  34. logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
  35. return null;
  36. }
  37. ProviderManager providerManager = new ProviderManager(authenticationProviders,
  38. parentAuthenticationManager);
  39. if (eraseCredentials != null) {
  40. providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
  41. }
  42. if (eventPublisher != null) {
  43. providerManager.setAuthenticationEventPublisher(eventPublisher);
  44. }
  45. providerManager = postProcess(providerManager);
  46. return providerManager;
  47. }
  48. }
  1. 首先,我们可以通过parentAuthenticationManager方法来给一个AuthenticationManager设置parent
  2. inMemoryAuthenticationjdbcAuthentication以及userDetailsService方法作用就是为了配置数据源。
  3. 最后就是performBuild方法,这个方法的作用就是根据当前AuthenicationManagerBuilder来构建一个AuthenticationManager,AuthenticationManager本身是一个接口,它的默认实现是ProviderManager,所以构建出来的ProviderManager。在构建ProviderManager时,一方面传入authenticationProviders,就是该ProviderManager所管理的所有的AuthenticationProvider,另一方面传入ProviderManagerparent(其实也就是一个ProviderManager)。

AuthenicationManagerBuilder还有一个实现类叫做DefaultPasswordEncoderAuthenticationManagerBuilder,作为内部类分别定义在WebSecurityConfigurerAdapterAuthenticationConfiguration中,不过DefaultPasswordEncoderAuthenticationManagerBuilder中的内容比较简单,重写了父类AuthenicationManagerBuilder的几个方法,配置了新的PasswordEncoder,这里就不列出源码了。但是这并不表示DefaultPasswordEncoderAuthenticationManagerBuilder类就不重要,因为在后面的使用中,基本上都是使用AuthenicationManagerBuilder的子类DefaultPasswordEncoderAuthenticationManagerBuilder
说了这么多,那么什么时候通过AuthenicationManagerBuilder来构建AuthenticationManager呢?🤔
在初始化流程中,得先介绍一个AuthenticationConfiguration类,这个类可以当作是一个全局配置类来理解,里面都是一些全局属性的配置:

  1. @Configuration(proxyBeanMethods = false)
  2. @Import(ObjectPostProcessorConfiguration.class)
  3. public class AuthenticationConfiguration {
  4. @Bean
  5. public AuthenticationManagerBuilder authenticationManagerBuilder(
  6. ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
  7. LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
  8. AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);
  9. DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
  10. if (authenticationEventPublisher != null) {
  11. result.authenticationEventPublisher(authenticationEventPublisher);
  12. }
  13. return result;
  14. }
  15. @Bean
  16. public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
  17. ApplicationContext context) {
  18. return new EnableGlobalAuthenticationAutowiredConfigurer(context);
  19. }
  20. @Bean
  21. public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
  22. return new InitializeUserDetailsBeanManagerConfigurer(context);
  23. }
  24. @Bean
  25. public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
  26. return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
  27. }
  28. public AuthenticationManager getAuthenticationManager() throws Exception {
  29. if (this.authenticationManagerInitialized) {
  30. return this.authenticationManager;
  31. }
  32. AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
  33. if (this.buildingAuthenticationManager.getAndSet(true)) {
  34. return new AuthenticationManagerDelegator(authBuilder);
  35. }
  36. for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
  37. authBuilder.apply(config);
  38. }
  39. authenticationManager = authBuilder.build();
  40. if (authenticationManager == null) {
  41. authenticationManager = getAuthenticationManagerBean();
  42. }
  43. this.authenticationManagerInitialized = true;
  44. return authenticationManager;
  45. }
  46. @Autowired
  47. public void setApplicationContext(ApplicationContext applicationContext) {
  48. this.applicationContext = applicationContext;
  49. }
  50. @Autowired
  51. public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
  52. this.objectPostProcessor = objectPostProcessor;
  53. }
  54. private static class EnableGlobalAuthenticationAutowiredConfigurer extends
  55. GlobalAuthenticationConfigurerAdapter {
  56. private final ApplicationContext context;
  57. private static final Log logger = LogFactory
  58. .getLog(EnableGlobalAuthenticationAutowiredConfigurer.class);
  59. EnableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) {
  60. this.context = context;
  61. }
  62. @Override
  63. public void init(AuthenticationManagerBuilder auth) {
  64. Map<String, Object> beansWithAnnotation = context
  65. .getBeansWithAnnotation(EnableGlobalAuthentication.class);
  66. if (logger.isDebugEnabled()) {
  67. logger.debug("Eagerly initializing " + beansWithAnnotation);
  68. }
  69. }
  70. }
  71. }
  1. 这里首先构建了一个AuthenicationManagerBuilder实例,这个实例就是用来构建全局AuthenticationManager,具体的构建过程在下面的getAuthenticationManager方法中。不过这里的这个全局的AuthenicationManagerBuilder并非总是有用,为什么这么说呢?请接着往下看。
  2. 另外还有一些initializeXXX方法,这些方法可以作为一个了解,因为正常情况下是不会用到这几个Bean的,只有当getAuthenticationManager方法被调用时,这些默认的Bean才会被配置,而getAuthenticationManager方法被调用,意味着我们要使用系统默认配置的AuthenticationManager作为parent,而在实际使用中,我们一般不会使用系统默认配置的AuthenticationManager作为parent,我们多多少少都会重新定制一下(使用WebSecurityConfigurerAdapter#congiure方法,为什么是这个方法,接着看)。

这些全局的Bean虽然一定会初始化,但是并非一定会用到。那么到底什么时候用到,什么时候用不到呢?这就和WebSecurityConfigurerAdapter有关了,在WebSecurityConfigurerAdapter中有三个重要的方法涉及到AuthenticationManager的初始化问题。
第一个是setApplicationContext方法:

  1. public void setApplicationContext(ApplicationContext context) {
  2. this.context = context;
  3. ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class);
  4. LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);
  5. authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);
  6. localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {
  7. @Override
  8. public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
  9. authenticationBuilder.eraseCredentials(eraseCredentials);
  10. return super.eraseCredentials(eraseCredentials);
  11. }
  12. @Override
  13. public AuthenticationManagerBuilder authenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
  14. authenticationBuilder.authenticationEventPublisher(eventPublisher);
  15. return super.authenticationEventPublisher(eventPublisher);
  16. }
  17. };
  18. }

在这个方法中,创建了两个几乎一模一样的AuthenicationManagerBuilder实例,为什么会有两个呢?第一个authenticationBuilder是一个局部的AuthenicationManagerBuilder,将来会传入到HttpSecurity中去构建局部的AuthenticationManager;第二个localConfigureAuthenticationBldr则是一个用来构建全局AuthenticationManagerAuthenicationManagerBuilder
📢在此处,有的小伙伴就会问了:构建全局的AuthenticationManager不是一开始就在AuthenticationConfiguration中创建了吗?为什么这里还会有一个?是的,当前这个localConfigureAuthenticationBldr是可以禁用的,如果禁用了,就会使用AuthenticationConfiguration中提供的AuthenticationManager,如果没有禁用的话,就是用localConfigureAuthenticationBldr来构建全局的AuthenticationManager
另一个方法是getHttp方法:

  1. protected final HttpSecurity getHttp() throws Exception {
  2. if (http != null) {
  3. return http;
  4. }
  5. AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
  6. localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
  7. AuthenticationManager authenticationManager = authenticationManager();
  8. authenticationBuilder.parentAuthenticationManager(authenticationManager);
  9. Map<Class<?>, Object> sharedObjects = createSharedObjects();
  10. http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
  11. sharedObjects);
  12. //省略
  13. return http;
  14. }

getHttp方法中,会首先调用authenticationManager方法去获取一个全局的AuthenticationManager,并调用parentAuthenticationManager方法给上面局部的authenticationBuilder中的parentAuthenticationManager属性设置parent,即全局的AuthenticationManager,然后在构建HttpSecurity的时候将局部authenticationBuilder传入进去。
那么关键性的问题就是authenticationManager方法到底是怎么执行的了:

  1. protected AuthenticationManager authenticationManager() throws Exception {
  2. if (!authenticationManagerInitialized) {
  3. configure(localConfigureAuthenticationBldr);
  4. if (disableLocalConfigureAuthenticationBldr) {
  5. authenticationManager = authenticationConfiguration
  6. .getAuthenticationManager();
  7. }
  8. else {
  9. authenticationManager = localConfigureAuthenticationBldr.build();
  10. }
  11. authenticationManagerInitialized = true;
  12. }
  13. return authenticationManager;
  14. }
  15. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  16. this.disableLocalConfigureAuthenticationBldr = true;
  17. }

可以看到,如果AuthenticationManager还没有初始化,那就先进行初始化。初始化首先调用configure方法,默认情况下,configure方法里面会把disableLocalConfigureAuthenticationBldr变量设置为true,这样接下来就会进入到if分支中,这个configure方法不知道大家有没有觉得眼熟?我们在自定义的SecurityConfig配置类继承WebSecurityConfigurerAdapter时,一般都会重写该方法,一旦重写了这个方法,那么disableLocalConfigureAuthenticationBldr变量就不会变成true,依然是false,这样在获取AuthenticationManager的时候就会进入到else分支。
如果进入到if分支中,就意味着开发者并没有重写configure方法,AuthenicationManagerBuilder就使用默认的,大家可以看到,此时调用authenticationConfiguration.getAuthenticationManager()方法去获取AuthenticationManager,也就是一开始我们说的那个全局的配置。
如果开发者重写了configure方法,意味着开发者对AuthenicationManagerBuilder进行了一些定制,此时就不能继续使用AuthenticationConfiguration中配置的默认的AuthenticationManager了,而要根据开发者的具体配置,调用localConfigureAuthenticationBldr.build方法去构建全局的AuthenticationManager
一句话,AuthenticationConfiguration中的配置有没有用上,全看开发着有没有重写WebSecurityConfigurerAdapter#congiure方法,重写了,就用localConfigureAuthenticationBldr来构建parent级别的AuthenticationManager,没重写,就用AuthenticationConfiguration中的方法来构建。
这是扮演parent角色的AuthenticationManager的构建过程,当然,parent并非必须,如果你没有这个需求,也可以不配置parent
最后来看下局部的AuthenticationManager是如何构建的,也就是和HttpSecurity绑定的那个AuthenticationManager
根据前面的介绍,HttpSecurity在构建的时候就会传入AuthenicationManagerBuilder,如下:

  1. public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
  2. AuthenticationManagerBuilder authenticationBuilder,
  3. Map<Class<?>, Object> sharedObjects) {
  4. super(objectPostProcessor);
  5. Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
  6. setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
  7. //省略
  8. }

传入进来的AuthenicationManagerBuilder,二话不说就存到SharedObject中,这个根据官方的解释,说它是一个在不同Configurer中共享的对象的工具,其实可以理解为一个缓存,现在存进去,需要的时候再取出来。
取出来的方法,在HttpSecurity中也已经定义好了,如下:

  1. private AuthenticationManagerBuilder getAuthenticationRegistry() {
  2. return getSharedObject(AuthenticationManagerBuilder.class);
  3. }

HttpSecurity中,凡是涉及到AuthenticationManager配置的,都会调用getAuthenticationRegistry方法,如下:

  1. public HttpSecurity userDetailsService(UserDetailsService userDetailsService)
  2. throws Exception {
  3. getAuthenticationRegistry().userDetailsService(userDetailsService);
  4. return this;
  5. }
  6. public HttpSecurity authenticationProvider(
  7. AuthenticationProvider authenticationProvider) {
  8. getAuthenticationRegistry().authenticationProvider(authenticationProvider);
  9. return this;
  10. }

最后在HttpSecuritybeforeConfigure方法中完成构建:

  1. @Override
  2. protected void beforeConfigure() throws Exception {
  3. setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
  4. }

至此,无论是全局的AuthenticationManager,还是局部的AuthenticationManager就都和大家捋一遍了。

6、小结

为什么每一个HttpSecurity都要绑定一个AuthenticationManager呢?
因为在同一个系统中,我们可以配置多个HttpSecurity,也就是多个不同的过滤器链,既然有多个过滤器链,每个请求到来的时候,他需要进入到某一个过滤器链中去处理,每个过滤器链中又会涉及到AuthenticationProvider的管理,不同的过滤器链中的AuthenticationProvider肯定是各自管理最为合适,也就是不同的过滤器链中都有一个绑定的AuthenticationManager,及每一个HttpSecurity都要绑定一个AuthenticationManager