重点标识
AuthenticationManager 默认是有parent的。
Security 向Spring容器注册了一个AuthenticationManager ,是一个全局的,也就是所谓的parent。
注册一个AuthenticationManager ,就是一个全局的
如果想要局部的,可以设置一个过滤器链。
我们平时配置的,都是局部的。
正常来说,请求到达的时候,都是局部的AuthenticationManager 来处理的,没配置,系统会自动配置。
如果这个AuthenticationManager 处理不了当前的认证,就会交给parent来处理。也就是所谓的全局的AuthenticationManager 。
代码演示
我们来看一下,自己配置两个局部过滤器链,然后分别登录,能不能达到我们想要的效果。
@Configuration
public class SecurityConfig {
/**
*
* 全局的AuthenticationManager 的用户就是这样配
* @return
*/
@Bean
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;
}
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容器中注册。
@Bean
public 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();
}
}
结语
枯燥,但是有用!