重点标识
AuthenticationManager 默认是有parent的。
Security 向Spring容器注册了一个AuthenticationManager ,是一个全局的,也就是所谓的parent。
注册一个AuthenticationManager ,就是一个全局的
如果想要局部的,可以设置一个过滤器链。
我们平时配置的,都是局部的。
正常来说,请求到达的时候,都是局部的AuthenticationManager 来处理的,没配置,系统会自动配置。
如果这个AuthenticationManager 处理不了当前的认证,就会交给parent来处理。也就是所谓的全局的AuthenticationManager 。
代码演示
我们来看一下,自己配置两个局部过滤器链,然后分别登录,能不能达到我们想要的效果。
@Configuration
public class SecurityConfig {/**** 全局的AuthenticationManager 的用户就是这样配* @return*/@BeanUserDetailsService globalUser(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("global").password("{noop}123").build());return inMemoryUserDetailsManager;}UserDetailsService adminUser(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("{noop}123").build());return inMemoryUserDetailsManager;}UserDetailsService rootUser(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").build());return inMemoryUserDetailsManager;}@Bean@Order(2)SecurityFilterChain securityRootFilterChain(HttpSecurity http) throws Exception {http//这里就是我们一开始说的,Security可以提供多个过滤器链,可以在这里进行分流,针对不同的情况,走不同的过滤器.securityMatcher("/root/**").authorizeHttpRequests(a->a.anyRequest().authenticated())//这里注意,由于过滤器链中存在AuthenticationManager 因此可以直接将数据源加载到过滤器链中.userDetailsService(rootUser()).formLogin(f ->f.loginProcessingUrl("/root/login").successHandler(((request, response, authentication) -> {response.getWriter().write(authentication.getName());})).permitAll()).csrf(c -> c.disable());return http.build();}@Bean@Order(1)SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.securityMatcher("/admin/**").authorizeHttpRequests(a->a.anyRequest().authenticated()).userDetailsService(adminUser()).formLogin(f ->f.loginProcessingUrl("/admin/login").successHandler(((request, response, authentication) -> {response.getWriter().write(authentication.getName());})).permitAll())//.csrf(c -> c.disable());return http.build();}
}
使用postMan测一下,全局的,没问题
root地址下的,也没问题。
但是,如果我们在root地址下,使用admin登录,那就有问题了。
但是,如果我们在admin地址下,登录admin,自然是没问题的
这就达成了我们想要的结果,两个局部的过滤器链,彻底分开,各自走各自的,同时,全局的过滤器链,两个都可以访问。
也验证了我们上一篇所了解的,AutehnticationManager中,如果找不到,就会去寻找它的父类里面的Provider了。
同时,我们可以看一下这部分源码,过滤器链都是通过HttpSecurity构建的,那我们可以想一下,HttpSecurity肯定不会是一个单例,不然写在多都会被覆盖,都是同一个。
进入到HttpSecurityConfiguration中,看一下,果然如此,prototype,可以有多个httpSecurity。
OK,我们再看一下这部分源码
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(this.objectPostProcessor, passwordEncoder);
建造者模式,构建了一个authenticationBuilder ,然后我们接着往下看。authenticationBuilder.parentAuthenticationManager(this.authenticationManager());
设置parent,这个parent就是 从Spring容器中拿的。
private AuthenticationManager authenticationManager() throws Exception {return this.authenticationConfiguration.getAuthenticationManager();}public AuthenticationManager getAuthenticationManager() throws Exception {if (this.authenticationManagerInitialized) {return this.authenticationManager;} else {AuthenticationManagerBuilder authBuilder = (AuthenticationManagerBuilder)this.applicationContext.getBean(AuthenticationManagerBuilder.class);if (this.buildingAuthenticationManager.getAndSet(true)) {return new AuthenticationManagerDelegator(authBuilder);} else {Iterator var2 = this.globalAuthConfigurers.iterator();while(var2.hasNext()) {GlobalAuthenticationConfigurerAdapter config = (GlobalAuthenticationConfigurerAdapter)var2.next();authBuilder.apply(config);}this.authenticationManager = (AuthenticationManager)authBuilder.build();if (this.authenticationManager == null) {this.authenticationManager = this.getAuthenticationManagerBean();}this.authenticationManagerInitialized = true;return this.authenticationManager;}}}
那它怎么保证Spring容器中一定存在AuthenticationManager呢,就是上面这部分,就是下面这部分代码,Bean,往Spring容器中注册。
@Beanpublic AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);AuthenticationEventPublisher authenticationEventPublisher = this.getAuthenticationEventPublisher(context);DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);if (authenticationEventPublisher != null) {result.authenticationEventPublisher(authenticationEventPublisher);}return result;}
上面这部分是基于Security提供的provider来实现的,我们来看自己配置的。
区别不是很大,有兴趣的,可以对照上面的进行测一下,效果是一样的。
@Configuration
public class SecurityConfig {/**** 全局的AuthenticationManager 的用户就是这样配* @return*/UserDetailsService globalUser(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("global").password("{noop}123").build());return inMemoryUserDetailsManager;}UserDetailsService adminUser(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("admin").password("{noop}123").build());return inMemoryUserDetailsManager;}AuthenticationManager adminAuthenticationManager(){DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setUserDetailsService(adminUser());ProviderManager providerManager = new ProviderManager(Arrays.asList(daoAuthenticationProvider),globalAuthenticationManager());return providerManager;}private AuthenticationManager globalAuthenticationManager() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setUserDetailsService(globalUser());ProviderManager providerManager = new ProviderManager(Arrays.asList(daoAuthenticationProvider));return providerManager;}AuthenticationManager rootAuthenticationManager(){DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setUserDetailsService(rootUser());ProviderManager providerManager = new ProviderManager(Arrays.asList(daoAuthenticationProvider),globalAuthenticationManager());return providerManager;}UserDetailsService rootUser(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").build());return inMemoryUserDetailsManager;}@Bean@Order(2)SecurityFilterChain securityRootFilterChain(HttpSecurity http) throws Exception {http//这里就是我们一开始说的,Security可以提供多个过滤器链,可以在这里进行分流,针对不同的情况,走不同的过滤器.securityMatcher("/root/**").authorizeHttpRequests(a->a.anyRequest().authenticated())//这里注意,由于过滤器链中存在AuthenticationManager 因此可以直接将数据源加载到过滤器链中// .userDetailsService(rootUser()).authenticationManager(rootAuthenticationManager()).formLogin(f ->f.loginProcessingUrl("/root/login").successHandler(((request, response, authentication) -> {response.getWriter().write(authentication.getName());})).permitAll()).csrf(c -> c.disable());return http.build();}@Bean@Order(1)SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.securityMatcher("/admin/**").authorizeHttpRequests(a->a.anyRequest().authenticated()).authenticationManager(adminAuthenticationManager())// .userDetailsService(adminUser()).formLogin(f ->f.loginProcessingUrl("/admin/login").successHandler(((request, response, authentication) -> {response.getWriter().write(authentication.getName());})).permitAll())//.csrf(c -> c.disable());return http.build();}
}
结语
枯燥,但是有用!