SpringSecurity之ProviderManager是如何产生的,如何与UsernamePasswordAuthenticationFilter光联在一起

本文主要探讨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;
    }

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值