本文内容与上篇文章深入理解AuthenticationManagerBuilder(源码篇)内容强关联,所以强烈建议先学习上篇文章内容,再来看本文,就会好理解很多!

1、抛砖引玉

  1. @Configuration
  2. public class SecurityConfig {
  3. @Bean
  4. UserDetailsService us() {
  5. InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
  6. manager.createUser(User.withUsername("sang").password("{noop}123").roles("admin").build());
  7. return manager;
  8. }
  9. @Configuration
  10. @Order(1)
  11. static class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {
  12. UserDetailsService us1() {
  13. InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
  14. manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin", "aaa", "bbb").build());
  15. return manager;
  16. }
  17. @Override
  18. protected void configure(HttpSecurity http) throws Exception {
  19. http.antMatcher("/foo/**")
  20. .authorizeRequests()
  21. .anyRequest().hasRole("admin")
  22. .and()
  23. .formLogin()
  24. .loginProcessingUrl("/foo/login")
  25. .permitAll()
  26. .and()
  27. .userDetailsService(us1())
  28. .csrf().disable();
  29. }
  30. }
  31. @Configuration
  32. @Order(2)
  33. static class DefaultWebSecurityConfig2 extends WebSecurityConfigurerAdapter {
  34. UserDetailsService us2() {
  35. InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
  36. manager.createUser(User.withUsername("江南一点雨").password("{noop}123").roles("user", "aaa", "bbb").build());
  37. return manager;
  38. }
  39. @Override
  40. protected void configure(HttpSecurity http) throws Exception {
  41. http.antMatcher("/bar/**")
  42. .authorizeRequests()
  43. .anyRequest().hasRole("user")
  44. .and()
  45. .formLogin()
  46. .loginProcessingUrl("/bar/login")
  47. .permitAll()
  48. .and()
  49. .csrf().disable()
  50. .userDetailsService(us2());
  51. }
  52. }
  53. }

此处定义了两个过滤器链。但是在每个过滤器中,又都提供了一个UserDetailsService实例,然后在configure(HttpSecurity http)方法中,配置了这个UserDetailsService实例。除了每一个过滤器链中都配置了一个UserDetailsService外,还提供了一个UserDetailsService的Bean,所以这里前前后后相当于一共有三个用户,那么我们在登录的时候,使用哪个用户可以登录成功呢?
先说结论:

  • 当登录地址是/foo/login,那么sangjavaboy两个用户可以登录成功
  • 当登录地址是/bar/login,那么sang江南一点雨两个用户可以登录成功

也就是说,那个全局的,公共的UserDetailsService总是有效的,而针对不同过滤器链配置的UserDetailsService则只针对当前过滤器链生效。

2、源码分析

2.1、全局AuthenticationManager

首先大家注意,虽然定义了两个过滤器链,但是在两个过滤器链的定义中,都没有重写WebSecurityConfigurerAdapter#congiure方法,结合上篇文章,没有重写这个方法,就意味着AuthenticationConfiguration中提供的全局AuthenticationManager是有效的,也就是说,系统默认提供的AuthenticationManager将作为其他局部AuthenticationManagerparent
那么我们来看下全局的AuthenticationManager都配置了啥?

  1. public AuthenticationManager getAuthenticationManager() throws Exception {
  2. if (this.authenticationManagerInitialized) {
  3. return this.authenticationManager;
  4. }
  5. AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
  6. if (this.buildingAuthenticationManager.getAndSet(true)) {
  7. return new AuthenticationManagerDelegator(authBuilder);
  8. }
  9. for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
  10. authBuilder.apply(config);
  11. }
  12. authenticationManager = authBuilder.build();
  13. if (authenticationManager == null) {
  14. authenticationManager = getAuthenticationManagerBean();
  15. }
  16. this.authenticationManagerInitialized = true;
  17. return authenticationManager;
  18. }

全局的配置中,有一步就是遍历globalAuthConfigurers,遍历全局的xxxConfigurer,并进行配置。全局的xxxConfigurer一共有三个,分别是:

  • EnableGlobalAuthenticationAutowiredConfigurer
  • InitializeUserDetailsBeanManagerConfigurer
  • InitializeAuthenticationProviderBeanManagerConfigurer

其中,InitializeUserDetailsBeanManagerConfigurer看名字就是用来配置UserDetailsService

  1. @Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
  2. class InitializeUserDetailsBeanManagerConfigurer
  3. extends GlobalAuthenticationConfigurerAdapter {
  4. @Override
  5. public void init(AuthenticationManagerBuilder auth) throws Exception {
  6. auth.apply(new InitializeUserDetailsManagerConfigurer());
  7. }
  8. class InitializeUserDetailsManagerConfigurer
  9. extends GlobalAuthenticationConfigurerAdapter {
  10. @Override
  11. public void configure(AuthenticationManagerBuilder auth) throws Exception {
  12. if (auth.isConfigured()) {
  13. return;
  14. }
  15. UserDetailsService userDetailsService = getBeanOrNull(
  16. UserDetailsService.class);
  17. if (userDetailsService == null) {
  18. return;
  19. }
  20. PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
  21. UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
  22. DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
  23. provider.setUserDetailsService(userDetailsService);
  24. if (passwordEncoder != null) {
  25. provider.setPasswordEncoder(passwordEncoder);
  26. }
  27. if (passwordManager != null) {
  28. provider.setUserDetailsPasswordService(passwordManager);
  29. }
  30. provider.afterPropertiesSet();
  31. auth.authenticationProvider(provider);
  32. }
  33. }
  34. }

可以看到,InitializeUserDetailsBeanManagerConfigurer中定义了内部类,在其内部类的configure方法中,通过getBeanOrNull去从容器中查找UserDetailsService实例,查找到后,创建DaoAuthenticationProvider,并最终配置给AuthenticationManagerBuilder对象。
这里的getBeanOrNull方法从容器中查找到的,实际上就是Spring容器中的Bean,也就是我们一开始配置了sang用户的那个Bean,这个Bean被交给了全局的AuthenticationManager,也就是所有局部AuthenticationManagerparent

2.2、局部AuthenticationManager

从上篇文章中可以知道所有HttpSecurity在构建的过程中,都会传递一个局部的AuthenticationManagerBuilder进来,这个局部的AuthenticationManagerBuilder一旦传入进来就存入了共享对象中,以后需要用的时候再从共享对象中取出来,部分代码如下:

  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. }
  9. private AuthenticationManagerBuilder getAuthenticationRegistry() {
  10. return getSharedObject(AuthenticationManagerBuilder.class);
  11. }

所以,在HttpSecurity中配置UserDetailsService,实际上是给这个AuthenticationManagerBuilder配置的:

  1. public HttpSecurity userDetailsService(UserDetailsService userDetailsService)
  2. throws Exception {
  3. getAuthenticationRegistry().userDetailsService(userDetailsService);
  4. return this;
  5. }

也就是局部AuthenticationManager。至此,整个流程就很清晰了。
深入理解AuthenticationManagerBuilder(源码篇二) - 图1
再结合上面的图说明一下,每一个过滤器链都会绑定一个自己的ProviderManager,即AuthenticationManager的实现,而每一个ProviderManager中都通过DaoAuthenticationProvider持有一个UserDetailsService对象,当开始认证的时候,首先由过滤器链所持有的局部ProviderManager去认证,要是认证失败了,则调用ProviderManagerparent去认证,此时就会用到全局AuthenticationManagerDaoAuthenticationProvider中的UserDetailsService对象了。
结合一开始的案例,例如你的登录地址是/foo/login,如果你的登录用户是sang/123,那么先去HttpSecurity的局部ProviderManager中去认证,结果验证失败(局部的ProviderManager中对应的用户是javaboy),此时就会进入局部ProviderManagerparent去认证,也就是全局认证,全局的ProviderManager中所对应的用户就是sang了,此时就认证成功!

3、再抛

再次修改SecurityConfig的定义,如下:

  1. @Configuration
  2. public class SecurityConfig {
  3. @Bean
  4. UserDetailsService us() {
  5. InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
  6. manager.createUser(User.withUsername("sang").password("{noop}123").roles("admin").build());
  7. return manager;
  8. }
  9. @Configuration
  10. @Order(1)
  11. static class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {
  12. UserDetailsService us1() {
  13. InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
  14. manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin", "aaa", "bbb").build());
  15. return manager;
  16. }
  17. @Override
  18. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  19. auth.userDetailsService(us1());
  20. }
  21. @Override
  22. protected void configure(HttpSecurity http) throws Exception {
  23. http.antMatcher("/foo/**")
  24. .authorizeRequests()
  25. .anyRequest().hasRole("admin")
  26. .and()
  27. .formLogin()
  28. .loginProcessingUrl("/foo/login")
  29. .permitAll()
  30. .and()
  31. .csrf().disable();
  32. }
  33. }
  34. @Configuration
  35. @Order(2)
  36. static class DefaultWebSecurityConfig2 extends WebSecurityConfigurerAdapter {
  37. UserDetailsService us2() {
  38. InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
  39. manager.createUser(User.withUsername("江南一点雨").password("{noop}123").roles("user", "aaa", "bbb").build());
  40. return manager;
  41. }
  42. @Override
  43. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  44. auth.userDetailsService(us2());
  45. }
  46. @Override
  47. protected void configure(HttpSecurity http) throws Exception {
  48. http.antMatcher("/bar/**")
  49. .authorizeRequests()
  50. .anyRequest().hasRole("user")
  51. .and()
  52. .formLogin()
  53. .loginProcessingUrl("/bar/login")
  54. .permitAll()
  55. .and()
  56. .csrf().disable();
  57. }
  58. }
  59. }

和前面相比,这段代码的核心变化,就是我重写了WebSecurityConfigurerAdapter#congiure方法,根据上篇文章的介绍,重写了该方法之后,默认的全局AuthenticationManager就失效了,也就是说sang这个用户定义失效了,换言之,无论是/foo/login还是/bar/login,使用sang/123现在都无法登录了。
在每一个HttpSecurity过滤器链中,都重写了WebSecurityConfigurerAdapter#congiure方法,并且配置了UserDetailsService,这个重写,相当于我在定义parent级别的ProviderManager。而每一个HttpSecurity过滤器链则不再包含UserDetailsService
当用户登录时,先去找到HttpSecurity过滤器链中的ProviderManager去认证,结果认证失败了,然后再找到ProviderManagerparent去认证,就成功了!