本文主要探讨ProviderManager是如何被创建的,并且如何和UserNamePasswordAuthenticationFilter过滤器关联在一起的。
1.我的环境如下:
1.1 POM文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.tophere.security</groupId>
<artifactId>demo01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo01</name>
<description>Demo project for Spring Boot</description>
<packaging>war</packaging>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.2 SpringSecurity配置类如下,本次采用的方式是不继承WebSecurityConfigurerAdapter去配置HttpSecurity,源码也是按照这个方式去讲解如何创建ProviderManager
注意:如果采用配置类继承WebSecurityConfigurerAdapter去配置HttpSecurity,那么部分源码解读不一样,但大致流程都是一样的。
1.3 Contoller内容
@RestController
public class MyTest01Controller {
@GetMapping("/demo01")
public void demo01(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletOutputStream outputStream = response.getOutputStream();
outputStream.print("abc");
}
@RequestMapping("/authensucce")
public String returnToken(){
return "认证通过,返回token";
}
@RequestMapping("/authenerror")
public String authenError(){
return "认证失败,请重新认证";
}
}
2.首先简单介绍下ProviderManager
当一个认证请求过来时,也就是表单提交的地址,他会经过一个过滤器叫做UsernamePasswordAuthenticationFilter(这个是一个Filter)去处理,这个Filter里面有个属性是AuthenticationManager,它的实现类就是ProviderManager(姑且这么记得,后面看源码的时候就知道实际不是存在AuthenticationManager这个属性里),这个实现类里有个属性叫做List<AuthenticationProvider> providers,providers里面有各种认证处理的提供方,每一个认证模式都会对应一个AuthenticationProvider,比如你使用表单的用户名密码去认证,那么对应的AuthenticationProvider的实现类就是DaoAuthenticationProvider,你输入用户名和密码提交时,他就去使用DaoAuthenticationProvider.authenticate方法去验证用户名和密码,验证成功后就转到 /authensucce这个请求路径下去处理,如果你是人脸识别或者其他认证方式,那么他就对应其他的AuthenticationProvider的实现类。
因此弄清楚这个ProviderManager是怎么创建的,怎么和UsernamePasswordAuthenticationFilter关联在一起的,是很重要的。
@Configuration
public class 不使用继承adpater {
@Bean
public SecurityFilterChain mysecurity(HttpSecurity httpSecurity) throws Exception {
//配置httpSecurity
DefaultSecurityFilterChain defaultSecurityFilterChain = httpSecurity.
authorizeRequests().anyRequest().authenticated()
.and()
//调用formLogin这个会创建一个UsernamePasswordAuthenticationFilter
.formLogin()
//认证请求,用于前后端分离时,表单提交的地址(action="/cyq")
.loginProcessingUrl("/cyq")
//表单提交时用户名输入框 name属性=username
.usernameParameter("username")
//表单提交时密码输入框name 属性=pass
.passwordParameter("pass")
//认证成功后,交给/authensucce请求路径处理
.successForwardUrl("/authensucce")
//认证失败后,交给/authenerror请求路径处理
.failureForwardUrl("/authenerror")
.and()
//关闭跨域请求
.csrf().disable()
//执行构建器,返回DefaultSecurityFilterChain对象
.build();
return defaultSecurityFilterChain;
}
}
@Configuration
class MyWebSecurityCustomizer implements WebSecurityCustomizer{
//配置WebSecurity
@Override
public void customize(WebSecurity web) {
//忽略/demo02路径和/error路径
web.ignoring().antMatchers("/demo01").antMatchers("/error");
}
}
主题开始了,请屏住呼吸,3、2、1 go....
3.铺垫
3.1 首先在spring-boot-autoconfigure-2.6.4.jar!\META-INF\spring.factories里有个SecurityAutoConfiguration类,这个在SpringBoot启动时就会自动加载,这是SpringBoot知识,就不多说介绍,点进去
3.2 重点看WebSecurityEnablerConfiguration类,SpringBoot会自动导入这个类到Spring容器中,点进去...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
//重点看WebSecurityEnablerConfiguration,SpringBoot会自动导入这个类到Spring容器中
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
SecurityDataConfiguration.class, ErrorPageSecurityFilterConfiguration.class })
public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
3.3 重点看@EnableWebSecurity这个注解,点击去
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//重点看这个注解,点击去
@EnableWebSecurity
class WebSecurityEnablerConfiguration {
}
3.4 还没到底,重点看HttpSecurityConfiguration.class这个类,然后再点击@EnableGlobalAuthentication,重点看AuthenticationConfiguration这个类,其实其他类也很重要,但是本次主要围绕这两个类进行探讨,其他的再后续文章中一一介绍
总结下就是,SpringBoot在启动时,会自动的把这两个类加载到Spring容器中,并执行内部的@Bean等标注的方法,并把方法返回的对象注入到Spring容器中(容器想象成存放一堆对象的数组即可)。关于怎么自动加载的,就不是本次探讨的范围,你死记硬背也要记住是自动加载的
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//重点看HttpSecurityConfiguration.class
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
HttpSecurityConfiguration.class })
//这个再点进去
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//重点看AuthenticationConfiguration这个类
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
4.5分钟 later....中场开始了
4.1 来瞄一下HttpSecurityConfiguration.class,代码都很重要,全复制出来
首先看httpSecurity()方法体,含有注解@Bean,说明SpringBoot在启动时,扫描这个类,并把httpSecurity()方法返回的HttpSecurity对象,注入到Spring容器中,因此在启动时,这个方法体内的代码都会自动执行。
@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration {
private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.";
private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";
private ObjectPostProcessor<Object> objectPostProcessor;
private AuthenticationManager authenticationManager;
private AuthenticationConfiguration authenticationConfiguration;
private ApplicationContext context;
@Autowired
void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Autowired
void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
this.authenticationConfiguration = authenticationConfiguration;
}
@Autowired
void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
this.context);
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
authenticationBuilder.parentAuthenticationManager(authenticationManager());
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
// @formatter:off
http
.csrf(withDefaults())
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling(withDefaults())
.headers(withDefaults())
.sessionManagement(withDefaults())
.securityContext(withDefaults())
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
// @formatter:on
return http;
}
private AuthenticationManager authenticationManager() throws Exception {
return (this.authenticationManager != null) ? this.authenticationManager
: this.authenticationConfiguration.getAuthenticationManager();
}
private Map<Class<?>, Object> createSharedObjects() {
Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context);
return sharedObjects;
}
}
4.1.1 以下对上面代码进行拆解讲解,以免看多了,烦
> 1. DefaultPasswordEncoderAuthenticationManagerBuilder,这个是构建AuthenticationManager的构建器,也就是执行这个对象的.buid方法,就会返回一个AuthenticationManager对象
>2.紧接着先调用authenticationManager()方法,这个方法其实会返回AuthenticationManager对象,然后把这个对象赋值给DefaultPasswordEncoderAuthenticationManagerBuilder的属性是AuthenticationManager parentAuthenticationManager,这样创建的AuthenticationManager对象就和DefaultPasswordEncoderAuthenticationManagerBuilder这个对象绑定在一起
HttpSecurity httpSecurity() throws Exception {
WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
this.context);
//1.这里创建了一个AuthenticationManagerBuilder,主要用于构建AuthenticationManager
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
//看authenticationManager()这个方法体,会返回一个provideManager,最后设置到authenticationBuilder里面,记住authenticationBuilder其实在spring bean容器里面可以自己获取
authenticationBuilder.parentAuthenticationManager(authenticationManager());
//将authenticationBuilder给httpsecurity对象
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
4.1.2 authenticationManager(),点进去看
this指的是HttpSecurityConfiguration.class这个类对象啊,这个类的对象也有一个属性类型为AuthenticationManager,这个对象刚被SpringBoot创建出来,因此this.authenticationManager这个肯定为null,也就是执行getAuthenticationManager()方法
private AuthenticationManager authenticationManager() throws Exception {
//看getAuthenticationManager这个方法,是AuthenticationConfiguration这个类的,这个类在spring启动时就被调用
return (this.authenticationManager != null) ? this.authenticationManager
: this.authenticationConfiguration.getAuthenticationManager();
}
this.authenticationConfiguration这个对象,看HttpSecurityConfiguration里的方法,@Autowired这个注解,表示参数AuthenticationConfiguration对象会自动从Spring容器中找到,并自动调用setAuthenticationConfiguration方法,并赋值给this.authenticationConfiguration
那AuthenticationConfiguration对象在哪里注入到spring容器呢?
3.4章节的总结部分还记得吗?我猜你早就忘记了,我的心在滴血中,急需你的补血良药...
@Autowired
void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
this.authenticationConfiguration = authenticationConfiguration;
}
备注下:以上的方法体都是在HttpSecurityConfiguration这个类里
4.1.3 getAuthenticationManager()方法,因为是this.authenticationConfiguration这个对象调用的,也就是AuthenticationConfiguration这个类的对象,因此我们就会跳转到AuthenticationConfiguration这个类,点进去
>1. 重点看authBuilder.build()这个方法,这个方法会创建一个AuthenticationManager对象。
重新申明一下,怕你们忘记了,我们一直在探讨如何创建AuthenticationManager对象,不要忘记,忘记就乱了
>2.那么authBuilder又是什么鬼
看到 AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);这句话了吗?
哦,是的,他是从Spring容器中获取到AuthenticationManagerBuilder.class这个类型的对象,也是用于构建AuthenticationManage,也就是说在SpringBoot启动时,就已经创建并初始化这个对象到Spring容器中了,其实跟4.1.1 的第二步的作用是一样的,就是构建AuthenticationManager对象的构建器而已。
public AuthenticationManager getAuthenticationManager() throws Exception {
if (this.authenticationManagerInitialized) {
return this.authenticationManager;
}
//这个是AuthenticationManagerBuilder,也是用于构建AuthenticationManage
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
if (this.buildingAuthenticationManager.getAndSet(true)) {
return new AuthenticationManagerDelegator(authBuilder);
}
//几个有关于DefaultPasswordEncoderAuthenticationManagerBuilder的配置类加载进去,回头对authenticationManager进行初始化操作,很重要,但是跟这篇文章不搭噶,暂时不管
for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
authBuilder.apply(config);
}
//然后进行构建,这个构建就很重要了,会返回authenticationManager,并赋值给AuthenticationConfiguration的authenticationManager属性
this.authenticationManager = authBuilder.build();
if (this.authenticationManager == null) {
this.authenticationManager = getAuthenticationManagerBean();
}
this.authenticationManagerInitialized = true;
return this.authenticationManager;
}
>3.番外篇:
那灵魂拷问下,SpringBoot是在哪里把这个Build对象给加载到Spring容器中的?
在AuthenticationConfiguration这个类里还有一段代码加了@Bean,且类型就是AuthenticationManagerBuilder,因此上面灵魂拷问的部分,没错就是这个对象了,也是new
DefaultPasswordEncoderAuthenticationManagerBuilder这个构建器用于构建AuthenticationManager对象。
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
ApplicationContext context) {
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
AuthenticationEventPublisher.class);
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
result.authenticationEventPublisher(authenticationEventPublisher);
}
//最终返回这个构建器,并放入Spring容器中
return result;
}
>4.回归主线,看 第2步骤的 authBuilder.build();这个方法,就跳到了DefaultPasswordEncoderAuthenticationManagerBuilder这个类的build()方法,实际上你点击build()方法的时候,会跳到这个类的父类AbstractConfiguredSecurityBuilder的父类AbstractSecurityBuilder(爷爷)里,然后它又去执行doBuild()方法,点进去
@Override
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
>5.dobuild方法是在它的父类AbstractConfiguredSecurityBuilder里面,所以你又跳到AbstractConfiguredSecurityBuilder的dobuild里,跳来跳去很烦是不是,但你记住这个流程,Build流程是SpringSecurity启动的核心部分,基本逃不开他,多看几遍,耐心耐心
查看dobuild方法的时候,核心的部分就是会执行performBuild(),当然其他的init()、config()也很重要,但是本次还是只是讨论AuthenticationManager怎么创建的,先忽略
@Override
protected final O doBuild() throws Exception {
synchronized (this.configurers) {
this.buildState = BuildState.INITIALIZING;
beforeInit();
init();
this.buildState = BuildState.CONFIGURING;
beforeConfigure();
//内部有for循环调用各个configrers里面的config(AuthenticationManagerBuilder)方法,其中有个InitialUserDeTailService...的配置类很重要,里面就是要获取UserDetail...和EncodingPassword等信息,回头看
configure();
this.buildState = BuildState.BUILDING;
// 这句话很重要,执行DefaultPasswordEncoderAuthenticationManagerBuilder的performBuild()方法
O result = performBuild();
this.buildState = BuildState.BUILT;
return result;
}
}
> 6. performBuild()方法其实是在DefaultPasswordEncoderAuthenticationManagerBuilder这个类中实现的,因此我们又跳到DefaultPasswordEncoderAuthenticationManagerBuilder这个类的performBuild()方法
对的,创建了ProviderManager对象,这个对象就是AuthenticationManager的实现类,并且创建时给他属性赋值,一个是authenticationProviders,就是开头我们说的一堆认证提供者,这个后续文章再重点讲解,另一个就是他的parentAuthenticationManager,这个parentAuthenticationManager暂时忽略,也很重要的,但是暂时不讲。
@Override
protected ProviderManager performBuild() throws Exception {
if (!isConfigured()) {
this.logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
return null;
}
//重点在这里,这里创建一个providerManger对象,也就是AuthenticationManager的实现类,authenticationProviders 暂时不管,回头补充,就是provideManger里面有多个authenticationProviders
//parentAuthenticationManager意思是如果ProviderManager里的authenticationProviders没有找到对应的验证机制,那么就去父类再去查找
ProviderManager providerManager = new ProviderManager(this.authenticationProviders,
this.parentAuthenticationManager);
if (this.eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);
}
if (this.eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(this.eventPublisher);
}
providerManager = postProcess(providerManager);
//最后返回一个AutenticationManager
return providerManager;
} 作者:CYQ_T https://www.bilibili.com/read/cv15806415 出处:bilibili
//重点在这里,这里创建一个providerManger对象,也就是AuthenticationManager的实现类,authenticationProviders 暂时不管,回头补充,就是provideManger里面有多个authenticationProviders
//parentAuthenticationManager意思是如果ProviderManager里的authenticationProviders没有找到对应的验证机制,那么就去父类再去查找
ProviderManager providerManager = new ProviderManager(this.authenticationProviders,
this.parentAuthenticationManager);
if (this.eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);
}
if (this.eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(this.eventPublisher);
}
providerManager = postProcess(providerManager);
//最后返回一个AutenticationManager
return providerManager;
}
5.结束前篇
5.1 至此就返回了一个ProviderManager对象,我估计你们忘记这个是哪个方法的返回结果。
再返回到4.1.1的代码,HttpSecurityConfiguration类里,其实上面我们一直在研究authenticationManager()这个内部的一些方法,最后是返回一个ProviderManager对象的
然后调用AuthenticationManagerBuilder的parentAuthenticationManager方法,把ProviderManager赋值给AuthenticationManagerBuilder对象的AuthenticationManager parentAuthenticationManager属性
这样ProviderManager就和AuthenticationManagerBuilder的实现类对象DefaultPasswordEncoderAuthenticationManagerBuilder关联在一起了
然后创建一个HttpSecurity对象,并把authenticationBuilder(即DefaultPasswordEncoderAuthenticationManagerBuilder)赋值给HttpSecurity
这样HttpSecurity对象就和DefaultPasswordEncoderAuthenticationManagerBuilder绑定
,也就和ProviderManager绑定
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
@Bean(HTTPSECURITY_BEAN_NAME)
//因为是原型类型,所以每次从容器中获取都会是新的对象
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
this.context);
//1.这里创建了一个AuthenticationManagerBuilder,主要用于构建AuthenticationManager
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
//看authenticationManager()这个方法体,会返回一个provideManager,最后设置到authenticationBuilder里面
authenticationBuilder.parentAuthenticationManager(authenticationManager());
//将authenticationBuilder给httpsecurity对象
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
http
.csrf(withDefaults())
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling(withDefaults())
.headers(withDefaults())
.sessionManagement(withDefaults())
.securityContext(withDefaults())
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
// @formatter:on
return http;
番外篇:
点进去HttpSecurity的构造器,spring Bean容器中有个Httpsecurity的对象,这个对象里面有个属性叫做
Map<Class<?>, Object> sharedObjects = new HashMap<>(),这个属性其实是在AbstractConfiguredSecurityBuilder父类里,最终是把DefaultPasswordEncoderAuthenticationManagerBuilder这个对象给加到sharedObjects 里面的,记住了,记住了,后面会用的到。
注意,HttpSecurity还有一个属性叫做private AuthenticationManager authenticationManager;而我们创建的并不是赋值给他。
public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
AuthenticationManagerBuilder authenticationBuilder, Map<Class<?>, Object> sharedObjects) {
super(objectPostProcessor);
Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
for (Map.Entry<Class<?>, Object> entry : sharedObjects.entrySet()) {
setSharedObject((Class<Object>) entry.getKey(), entry.getValue());
}
ApplicationContext context = (ApplicationContext) sharedObjects.get(ApplicationContext.class);
this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
}
好了,至此,HttpSecurity和DefaultPasswordEncoderAuthenticationManagerBuilder和ProvideManager的关系就建立起来了,但是还没有跟UserNamePasswordAuthenticationFilter建立起关系,灵魂拷问,怎么建立关系
6.结束中篇
还记得我们自己写的security配置类里往Spring容器中注入的SecurityFilterChain对象吗?
重新贴出来,回顾下:
这个类是有@Bean的,因此他的HttpSecurity参数就是从Spring容器中自动注入的,那这个对象注入到Spring容器中的步骤在4.1.1 里,我估计你们也忘记了,心在滴血2.0
并且会自动执行方法体内的方法。
@Configuration
public class 不使用继承adpater {
@Bean
public SecurityFilterChain mysecurity(HttpSecurity httpSecurity) throws Exception {
//配置httpSecurity
DefaultSecurityFilterChain defaultSecurityFilterChain = httpSecurity.
authorizeRequests().anyRequest().authenticated()
.and()
//调用formLogin这个会创建一个UsernamePasswordAuthenticationFilter
.formLogin()
//认证请求,用于前后端分离时,表单提交的地址(action="/cyq")
.loginProcessingUrl("/cyq")
//表单提交时用户名输入框 name属性=username
.usernameParameter("username")
//表单提交时密码输入框name 属性=pass
.passwordParameter("pass")
//认证成功后,交给/authensucce请求路径处理
.successForwardUrl("/authensucce")
//认证失败后,交给/authenerror请求路径处理
.failureForwardUrl("/authenerror")
.and()
//关闭跨域请求
.csrf().disable()
//执行构建器,返回DefaultSecurityFilterChain对象
.build();
return defaultSecurityFilterChain;
}
}
@Configuration
class MyWebSecurityCustomizer implements WebSecurityCustomizer{
//配置WebSecurity
@Override
public void customize(WebSecurity web) {
//忽略/demo02路径和/error路径
web.ignoring().antMatchers("/demo01").antMatchers("/error");
}
}
注意.formLogin()方法,它是创建FormLoginConfigurer配置文件,并且加入到spring容器中HttpSecurity对象的configurers = new LinkedHashMap<>()这个属性里,具体细节后续再写文章讲解,先这样记着,有能力的话点进去自己看下。
最后注意.build()方法,灵魂方法。
build()里面最后都会执行dobuild方法,我说过构建器的build流程是非常重要的,处处都要使用到,流程上面也讲过,这里就接着看
@Override
protected final O doBuild() throws Exception {
synchronized (this.configurers) {
this.buildState = BuildState.INITIALIZING;
beforeInit();
init();
this.buildState = BuildState.CONFIGURING;
//这个方法里会调用AuthenticationBuild实现类buid一个AutenticationMnanager然后压入到HttpSecurity的shareObjects里面
beforeConfigure();
//重点看这个方法
configure();
this.buildState = BuildState.BUILDING;
O result = performBuild();
this.buildState = BuildState.BUILT;
return result;
}
}
i.看beforConfigirure()方法,注意,这个调用对象是是HttpSecurity,因为我们现在是在HttpSecurity这个对象的build过程
setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());这里的getAuthenticationRegistry()就是HttpSecurity的shareObjects里的AuthenticationManagerBuilder对象,我们把DefaultPasswordEncoderAuthenticationManagerBuilder压入到new HttpSecurity这个对象里
这里getAuthenticationRegistry是从HttpSecurity这个对象取出DefaultPasswordEncoderAuthenticationManagerBuilder,然后执行build操作,就构建了一个AuthenticationManager对象,然后再把AuthenticationManager这个对象setSharedObject到HttpSecurity对象的sharedObjects这个hashMap里。记住这一个步骤,后面会用到sharedObjects
@Override
protected void beforeConfigure() throws Exception
{
if (this.authenticationManager != null)
{
setSharedObject(AuthenticationManager.class, this.authenticationManager);
}
else
{
setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
} }
----------------------点进去getAuthenticationRegistry()------------------------------ private AuthenticationManagerBuilder getAuthenticationRegistry() {
//从SharedObject这个属性里获取到AuthenticationManagerBuilder对象
return getSharedObject(AuthenticationManagerBuilder.class);
}
ii.configure();方法
里面是存在和UserNamePasswordAuthenticationFilter关联的操作
configure()点进去,他是一个for循环,循环调用各个configurers里面的configure(HttpSecurity)方法,其中跟UsernamePasswordAuthenticationFilter相关的配置类是FormLoginConfigurer,去看他的configure(HttpSecurity httpsecurity)方法
UsernamePasswordAuthenticationFilter为什么跟FormLoginConfigurer有关系,后面会介绍到 作者:CYQ_T https://www.bilibili.com/read/cv15806415 出处:bilibili
@SuppressWarnings("unchecked")
private void configure() throws Exception {
//这个是原本是linkMap类型的,key是类名,value是数组,因此把value多个数组合并成一个数组
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
//循环调用各个configurers里面的configure(HttpSecurity)方法,其中跟UsernamePasswordAuthenticationFilter相关的配置类是FormLoginConfigurer,去看他的configure(HttpSecurity)方法
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
7.结束终极篇
跳到FormLoginConfigurer类里的configure(AuthenticationBuild)方法,
这个方法在他的父类里AbstractAuthenticationFilterConfigurer里。
@Override
public void configure(B http) throws Exception {
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if (portMapper != null) {
this.authenticationEntryPoint.setPortMapper(portMapper);
}
RequestCache requestCache = http.getSharedObject(RequestCache.class);
if (requestCache != null) {
this.defaultSuccessHandler.setRequestCache(requestCache);
}
//这里是关键了,authFilter就是UsernamePasswordAuthenticationFilter,这个在我们设置httpsecurity.formLogin()时就创建了,点击formlogin方法,看到getOrApply(new FormLoginConfigurer<>());FormLoginConfigurer可以点进去看下,最终会有一个super(new UsernamePasswordAuthenticationFilter(), null);,再点super进去看this.authFilter = authenticationFilter;意思就是把UsernamePasswordAuthenticationFilter这个创建的对象赋值给FormLoginConfigurer的authFilter这个对象
//先执行http.getSharedObject(AuthenticationManager.class),还记得上面(第6部分的beforConfigirure()方法)我们刚刚把AuthenticationManager对象塞进HttpSecurity的sharedObjects吗?这时候就从这里获取这个ProvideManger(AuthenticationManager的实现类)出来,然后把这个provideManager对象设置到UsernamePasswordAuthenticationFilter的AuthenticationManager属性
//ok了,这就是把ProvideManager这个对象和UsernamePasswordAuthenticationFilter关联在一起,后面认证请求,认证请求,是认证请求,也就是httpsecurity.loginProcessingUrl("/cyq"),这个"/cyq"就是认证请求路径,用户前后端分离开发时,前端去传用户名和密码去认证的这个路径。认证请求到达这个过滤器时,就可以调用ProvidreManger这个对象去做认证处理了。
this.authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
this.authFilter.setAuthenticationSuccessHandler(this.successHandler);
this.authFilter.setAuthenticationFailureHandler(this.failureHandler);
if (this.authenticationDetailsSource != null) {
this.authFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
}
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
if (sessionAuthenticationStrategy != null) {
this.authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
if (rememberMeServices != null) {
this.authFilter.setRememberMeServices(rememberMeServices);
}
F filter = postProcess(this.authFilter);
http.addFilter(filter);
}
这里是关键了,authFilter就是UsernamePasswordAuthenticationFilter,这个在我们设置httpsecurity.formLogin()时就创建了,r点击formlogin方法,看到getOrapply(new FormLoginConfigurer<>());FormLoginConfigurer可以点进去看下,最终会有一个super(new UsernamePasswordAuthenticationFilter(), null);,再点super进去看this.authFilter = authenticationFilter;意思就是把UsernamePasswordAuthenticationFilter这个创建的对象赋值给FormLoginConfigurer的authFilter这个对象
先执行http.getSharedObject(AuthenticationManager.class),还记得上面(第6部分的beforConfigirure()方法)我们刚刚把AuthenticationManager对象塞进HttpSecurity的sharedObjects吗?这时候就从这里获取这个ProvideManger(AuthenticationManager的实现类)出来,然后把这个provideManager对象设置到UsernamePasswordAuthenticationFilter的AuthenticationManager属性
ok了,这就是把ProvideManager这个对象和UsernamePasswordAuthenticationFilter关联在一起,后面认证请求,认证请求,是认证请求,也就是httpsecurity.loginProcessingUrl("/cyq"),这个"/cyq"就是认证请求路径,用户前后端分离开发时,前端去传用户名和密码去认证的这个路径。认证请求到达这个过滤器时,就可以调用ProvidreManger这个对象去做认证处理了。
8.一个认证请求与UsernamePasswordAuthenticationFilter的爱恨情仇
一个认证请求过来后到达UsernamePasswordAuthenticationFilter的doFilter方法里,然后会调用attemptAuthentication(request,reqsponse)这个方法进行认证,点进去
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
//认证请求走以下代码
try {
//进入到attemptAuthentication这个代码里
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
// return immediately as subclass has indicated that it hasn't completed
return;
}
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// Authentication failed
unsuccessfulAuthentication(request, response, ex);
}
attemptAuthentication(request,reqsponse)点进去就来到了UsernamePasswordAuthenticationFilter这个类里,其中有一句话就是this.getAuthenticationManager().authenticate(authRequest);这个的getAuthenticationManager()方法不就是获取UsernamePasswordAuthenticationFilter的AuthenticationManager吗?
我们1-7探讨的不就是把AuthenticationManager(实现类是ProviderManager)如何设置到UsernamePasswordAuthenticationFilter里吗?
因此现在都串起来了吗?
后续认证就在AuthenticationManager里做一些具体的认证操作,后续章节会详细介绍认证的细节处理,尤其是AuthenticationProvider这个类。
此篇文章耗时3小时完成....Bye Bye
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
-----------------------------------this.getAuthenticationManager()-------------
protected AuthenticationManager getAuthenticationManager() {
return this.authenticationManager;
}