Spring Security初始化过程以及AuthenticationManager详解以及如何自定义认证方式 (1)

:::info

问题1:自定义Providers时注入的AuthenticationManager为null

::: AuthenticationManager这个对象中有一个providers属性,里面维护了全部的providers列表,这里问题很明显,并没有我们自定义的两个处理器,而是只有一个系统默认的DaoAuthenticationProvider,这就很奇怪了,明明在配置类中已经把他们维护进去了,为什么没有注册上?

AuthenticationManager在这里是个Bean,但它本身并不是,而是我手动构造进来的,回来看一下构造的地方是不是有什么问题。

java 复制代码
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

这段代码是从网上抄来的,因为很多文章都在讲需要这样注入。看起来也没什么问题,容器给提供了一个AuthenticationConfiguration,看起来似乎是个构造器,然后从中取到了AuthenticationManager实例,点进去也看不出个所以然,遇事先百度看看。'


AuthenticationManager的全局对象与本地对象

在筛选掉大量无用文章后,还真发现了一个和我问题几乎是一模一样的文章,在此附上链接: Spring Security 实战干货:AuthenticationManager的初始化细节 (多说一句,此作者的SpringSecurity专栏有大量的文章,讲解比较深入,不少文章给与了我非常大的启发,非常感谢此位作者) 其中第三节提供了一种解决办法,但是一开始说了,我并不是使用WebSecurityConfigurerAdapter作为配置基类的,所以这个继承的方法并不存在,虽然经我验证过确实可行(怎么验证的?改回使用WebSecurityConfigurerAdapter),但并没有完全解决我的问题。现在开始怀疑怀疑SpringSecurity的这两种配置方式是不是有什么区别,搜索重点转向SpringSecurity 5.7版本的配置变化(因为从5.7开始废弃了WebSecurityConfigurerAdapter)。

果然,在刚才那位作者的专栏中发现了一篇文章正好是我想要的,附上链接: Spring Security 实战干货:WebSecurityConfigurerAdapter即将被移除 文章中这样一句话惊到了我:

AuthenticationManager配置主要分为全局的(Global )、本地的(Local)

什么是全局的?本地又是什么?从来没有听过这两个词啊,接下来搜索重点就转向AuthenticationManager,重点查找全局与本地这两个关键字。搜索半天无果(搜索的过程又是一把心酸泪),还是在Spring的官方doc中发现了端倪,附上文档链接: Spring Providermanager

这里必须要讲一下AuthenticationManager和ProviderManager详细运行过程了,这里强烈推荐去阅读官方文档,尤其是提到的这一篇,全文都是重点,逐字逐句,中英对照的去看,收获非常多。捡官方的几张图简明扼要的说一下。

  1. ProviderManager是AuthenticationManager的默认实现,ProviderManager中会维护一个ProviderList,根据传入的Token类型进行逐级验证,直至走完整个链条。
  1. ProviderManager存在一个父级,在没有合适的Provider可以做验证的情况下会寻找父级AuthenticationManager实例,通常也是一个ProviderManager,去进行验证。 如果还是没有找到则会产生一个No Provider异常。
  1. 系统中允许存在多个ProviderManager,多个实例可以共享同一个AuthenticationManager父级,这意味着,系统中可以存在多个Security认证链。

看到这,突然有了一个想法,会不会,有一种可能,我配置出来的AuthenticationManager是那个父级? 再打个断点验证一下吧,看下面的parent,值是什么?null 这样一来基本可以断定了,我拿到的这个AuthenticationManager,是个最顶层的父级,而在代码中配置进去的那两个认证处理器实际上是给了Local的AuthenticationManager。问题是,我怎么才能拿到这个局部对象呢?


@EnableWebSecurity

现在已知信息有下面几点:

  1. ProviderManager这个东西的实例在系统中会存在两份,一份为全局,一份为局部。当然如果你配置多个拦截器链的话局部实例也会有多份;
  2. 经过测试(断点打在build()方法前一行即可),在SecurityFilterChain构造的过程中自定义的authticationProviders其实是注册成功的,并且它们只存在于Local ProviderManager;
  3. 全局的ProviderManager中只会存在一个默认的DaoAuthenticationProvider;
  4. 网上包括各个开源项目中,通过authenticationConfiguration.getAuthenticationManager()方法获得的ProviderManager对象一定是全局对象,不管是从spring官方文档中还是在javadoc中都提到了这一点。

已经搜不到任何有价值的文档了,该如何下手呢?只能硬着头皮啃源码了,那从哪里开始啃呢,就从全局的ProviderManager怎么出现的开始吧。

回到配置类,翻了一圈,@EnableWebSecurity这个注解比较可以,打开看看。里面有一个@EnableGlobalAuthentication,继续打开看。

The EnableGlobalAuthentication annotation signals that the annotated class can be used to configure a global instance of AuthenticationManagerBuilder. For example:

明确提到了这里在创建一个全局AuthenticationManagerBuilder,他必然和全局的ProviderManager有关联,看到那个@Import(AuthenticationConfiguration.class)了吗,继续点进去。 打眼一看,四个内部类,还有一堆的get、build、initialize字样的方法,感觉这里面大有乾坤,这里面内容太多太烧脑


已经发现了全局与局部ProviderManager的问题,接下来就是我们要看一看这个全局的ProviderManager是怎么出来的。 注意,ProviderManager是AuthenticationManager的默认实现,所以在文中两者会交叉出现,本质是一个东西,不要被绕晕。 深吸一口气清醒一下,开始翻源码吧。

AuthenticationConfiguration

AuthenticationConfiguration这个类中方法非常多,我们只看需要的,首先发现其中一个标记了@Bean的方法,他会直接向容器中放入对象,而这个对象正是我们需要的AuthenticationManagerBuilder。

java 复制代码
@Bean
	public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
			ApplicationContext context) {
		LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
		AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
		DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
				objectPostProcessor, defaultPasswordEncoder);
		if (authenticationEventPublisher != null) {
			result.authenticationEventPublisher(authenticationEventPublisher);
		}
		return result;
	}

这里干了三件事

  1. 获取一个密码编码器PasswordEncoder,还是懒加载的,没什么用;
  2. 获取了一个认证事件发布器AuthenticationEventPublisher,他是一个接口,其中的非空实现是DefaultAuthenticationEventPublisher,点进去会发现有很多认证消息相关的初始化操作,但我们并不需要;
  3. 获取了一个使用密码认证的AuthenticationManager对象DefaultPasswordEncoderAuthenticationManagerBuilder,隐约感觉这个Builder会跟那个DaoAuthenticationProvider会有什么关系,因为他也是默认的基于JDBC的验证处理器实现,也没什么帮助。

看下来他只是构造了一个Builder对象然后放在了容器里,继续往下看会发现一个get方法,正是用来获取AuthenticationManager对象的,相关说明我会标记在注释里面。

java 复制代码
	public AuthenticationManager getAuthenticationManager() throws Exception {
        // 根据初始化标记判断是否需要new
		if (this.authenticationManagerInitialized) {
			return this.authenticationManager;
		}
    	// 从容器当中获取上下文注册进去的AuthenticationManagerBuilder对象
		AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
		if (this.buildingAuthenticationManager.getAndSet(true)) {
			return new AuthenticationManagerDelegator(authBuilder);
		}
    	// 这里默认是个Collections.emptyList()
		for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
			authBuilder.apply(config);
		}
        // 再次构建
		this.authenticationManager = authBuilder.build();
		// 再次尝试获取AuthenticationManager实例
		if (this.authenticationManager == null) {
			this.authenticationManager = getAuthenticationManagerBean();
		}
    	// 修改初始化标识位
		this.authenticationManagerInitialized = true;
		return this.authenticationManager;
	}

调用此方法会返回一个默认的DefaultPasswordEncoderAuthenticationManagerBuilder所得到的AuthenticationManager。 这里暂告一段落,但是还记得我们的目的是什么吗?我们要找到这个父AuthenticationManager是怎么出来的,这里只是发现了实例的构造过程,那他是怎么成为"父"的呢?

HttpSecurityConfiguration

我们点击AuthenticationConfiguration.getAuthenticationManager()方法,在他的所有调用中一下子就看到了一个熟悉的类HttpSecurityConfiguration,他和下面的WebSecurityConfiguration都出现在@EnableWebSecurity这个注解的Import中,而且,我们使用的Security构造方法也是针对于HttpSecurity的,那我们重点来看看他。

java 复制代码
/*
 * Copyright 2002-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.config.annotation.web.configuration;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * {@link Configuration} that exposes the {@link HttpSecurity} bean.
 *
 * @author Eleftheria Stein
 * @since 5.4
 */
@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;

	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
			.getContextHolderStrategy();

	private ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy();

	@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;
	}

	@Autowired(required = false)
	void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
		this.securityContextHolderStrategy = securityContextHolderStrategy;
	}

	@Autowired(required = false)
	void setContentNegotiationStrategy(ContentNegotiationStrategy contentNegotiationStrategy) {
		this.contentNegotiationStrategy = contentNegotiationStrategy;
	}

	@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());
		authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
		HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
		WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
		webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
		// @formatter:off
		http
			.csrf(withDefaults())
			.addFilter(webAsyncManagerIntegrationFilter)
			.exceptionHandling(withDefaults())
			.headers(withDefaults())
			.sessionManagement(withDefaults())
			.securityContext(withDefaults())
			.requestCache(withDefaults())
			.anonymous(withDefaults())
			.servletApi(withDefaults())
			.apply(new DefaultLoginPageConfigurer<>());
		http.logout(withDefaults());
		// @formatter:on
		applyDefaultConfigurers(http);
		return http;
	}

	private AuthenticationManager authenticationManager() throws Exception {
		return (this.authenticationManager != null) ? this.authenticationManager
				: this.authenticationConfiguration.getAuthenticationManager();
	}

	private AuthenticationEventPublisher getAuthenticationEventPublisher() {
		if (this.context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
			return this.context.getBean(AuthenticationEventPublisher.class);
		}
		return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
	}

	private void applyDefaultConfigurers(HttpSecurity http) throws Exception {
		ClassLoader classLoader = this.context.getClassLoader();
		List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
				.loadFactories(AbstractHttpConfigurer.class, classLoader);
		for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
			http.apply(configurer);
		}
	}

	private Map<Class<?>, Object> createSharedObjects() {
		Map<Class<?>, Object> sharedObjects = new HashMap<>();
		sharedObjects.put(ApplicationContext.class, this.context);
		sharedObjects.put(ContentNegotiationStrategy.class, this.contentNegotiationStrategy);
		return sharedObjects;
	}

}

点进来,代码不长关键点可不少,从上面看有AuthenticationManager,还有AuthenticationConfiguration,还有最重要的HttpSecurity,还是个原型类型说明他并不是单例的,这也是SpringSecurity可以配置多套认证体系的关键,那自然,这也是初始化默认体系的地方了。分解看一下:

  1. new WebSecurityConfigurerAdapter.LazyPasswordEncoder(this.context);创建了一个密码编码器;
  2. new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(this.objectPostProcessor, passwordEncoder);创建了一个DefaultPasswordEncoderAuthenticationManagerBuilder实例对象
  3. 关键的一句authenticationBuilder.parentAuthenticationManager(authenticationManager());,这里给刚才的authenticationBuilder设置了父对象。父对象哪里来?先从当前的成员变量中获取,如果没有则调用getAuthenticationManager()方法进行获取。这个方法是哪里的?是刚才的AuthenticationConfiguration中的。这个方法干了什么?是返回了一个由DefaultPasswordEncoderAuthenticationManagerBuilder所创建的AuthenticationManager对象。

现在我们弄清了这个全局的AuthenticationManager是怎么出来的了,但是还有一个疑问没有解答,默认的DaoAuthenticationProvider又是怎么被加到这个全局AuthenticationManager中的呢?


HttpSecurity

在刚才的httpSecurity()方法中继续往下走,我们点开new HttpSecurity这一行看看,看看它的构造方法里都有什么。

  1. 上来就是一个super(),点进去之后,只是一些赋值操作,没什么含义;
  2. setSharedObject这是一个很有意思的地方,SpringSecurity自己维护了一个缓存map,可以把一些常用的对象放在里面,用的时候直接通过HttpSecurity实例进行获取;
  3. 接下来拿到了一个RequestMatcherConfigurer实例,提供给url配置用的,但好像也对我们没什么帮助。

到这里我陷入了一个困境,只是知道了有全局配置这回事,但是对于怎么获取到局部配置仍然不清楚。继续向下翻看,发现了在配置时用到的authenticationProvider()方法。

java 复制代码
@Override
	public HttpSecurity authenticationProvider(AuthenticationProvider authenticationProvider) {
		getAuthenticationRegistry().authenticationProvider(authenticationProvider);
		return this;
	}

getAuthenticationRegistry()是一个私有方法,点进去继续看,是从SharedObject中获取到了AuthenticationManagerBuilder对象。

java 复制代码
	private AuthenticationManagerBuilder getAuthenticationRegistry() {
		return getSharedObject(AuthenticationManagerBuilder.class);
	}

到这里,可以确定确定HttpSecurity和DefaultPasswordEncoderAuthenticationManagerBuilder以及父AuthenticationManager它们三者之间的关系了,但是仍然没有发现默认的provider是怎么加进去的,以及如何获取local AuthenticationManager。找到这已经花了我一天半的时间了。


回到SecurityFilterChain

其实在上面的搜索过程中我发现了另一种方法,还记得之前看到的如何定义全局的ProviderManager吗?事实上我也尝试过完全手动将ProviderManager构造出来然后注册为Bean,这方法是可行的。

但是这样做有一个极大的问题:这样会直接修改全局AuthenticationManager实例,Local AuthenticationManager其实还是原来那样,这样做能成功的原因仅仅是当local验证失败后自动转向父级验证而已。虽然问题能解决,但实在算不上好方法,没有解决根本问题。 Spring Security without the WebSecurityConfigurerAdapter 再打开这篇文章看看,突然发现,在我之前看到Global AuthenticationManager这一节下方,就介绍了Accessing the local AuthenticationManager,这不正是我想要的吗? 原来Spring早就给我们指了一条明路出来,只可惜当时太心急完全没有注意这里。但当我看完之后心又凉了半截,他说可以自定义一个DSL,可这DSL是个啥啊,跟随文档指引点开了下面的连接: CUSTOM DSL 写的非常简练,就是你可以balabala这样定义一个DSL,然后balabala这样使用它,然后还告诉你,HttpSecurity.authorizeRequests()就是这样实现的。感觉,什么也没说啊?我的AuthenticationManager呢?

来都来了,这里面不是继承了一个AbstractHttpConfigurer吗,管他干啥的先点开看看。一个抽象类,看一下实现吧,这一看了不得,里面有很多类非常熟悉啊。 只看名字,大概就能认出来这是SpringSecurity各种登录方式的默认实现,比如FormLogin,比如HttpBasic,比如OAuth等等,这和我现在要做的验证码、短信登录岂不正好是一样的,那就找一个最简单最贴切的表单登录FormLoginConfigurer来看看吧。


FormLoginConfigurer

先看注释 第一行就说明了这是框架默认的登录页,这个地方在哪用的呢?回头看Security的配置类。 第三行,由于我们要使用自定义的登陆接口,所以上来就把自带的FormLogin关掉了,点进去看看formLogin()这个方法。

java 复制代码
	public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
		return getOrApply(new FormLoginConfigurer<>());
	}

怎么样,正好是我们正在看的FormLoginConfigurer这个类,说明我们没找错,那就一点一点来啃这个配置类吧。全代码太长,主要是注释占了很大一部分,就不贴完整代码了,捡重要的说。 首先这是FormLoginConfigurer的无参构造器,后两行是在设置用户名和密码,没什么好说的,这个super(new UsernamePasswordAuthenticationFilter(), null)点开看看,先看里面的UsernamePasswordAuthenticationFilter。

java 复制代码
	/**
	 * Creates a new instance
	 * @see HttpSecurity#formLogin()
	 */
	public FormLoginConfigurer() {
		super(new UsernamePasswordAuthenticationFilter(), null);
		usernameParameter("username");
		passwordParameter("password");
	}

	@Override
	public FormLoginConfigurer<H> loginPage(String loginPage) {
		return super.loginPage(loginPage);
	}

	/**
	 * The HTTP parameter to look for the username when performing authentication. Default
	 * is "username".
	 * @param usernameParameter the HTTP parameter to look for the username when
	 * performing authentication
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public FormLoginConfigurer<H> usernameParameter(String usernameParameter) {
		getAuthenticationFilter().setUsernameParameter(usernameParameter);
		return this;
	}

	/**
	 * The HTTP parameter to look for the password when performing authentication. Default
	 * is "password".
	 * @param passwordParameter the HTTP parameter to look for the password when
	 * performing authentication
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public FormLoginConfigurer<H> passwordParameter(String passwordParameter) {
		getAuthenticationFilter().setPasswordParameter(passwordParameter);
		return this;
	}

点开super后注意我们来到了他的父类UsernamePasswordAuthenticationFilter中,DEFAULT_ANT_PATH_REQUEST_MATCHER是个常量,里面定义了一个POST /login接口,这也就是SpringSecurity默认登录地址的来源。里面还有个super,继续看。 现在我们又到了新的父类AbstractAuthenticationProcessingFilter中,乍一看这个构造方法是指做了个校验然后赋值而已,但是这个抽象类的说明非常值得我们一看。 看完这个说明其实我是有一点激动的,感觉已经很接近真相了。因为他明确提到了需要AuthenticationManager来处理验证请求,并且由attemptAuthentication()方法执行验证。还告诉我们,验证成功的话由AuthenticationSuccessHandler进行处理,验证失败由AuthenticationFailureHandler进行处理。

继续向下看,陆续会发现setAuthenticationManager(AuthenticationManager authenticationManager)、setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler)、setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler)等认证相关的方法,最关键的attemptAuthentication(HttpServletRequest request, HttpServletResponse response)方法是一个抽象方法,需要子类去实现。而authenticationManager恰好也是它的一个成员变量。 向上回到UsernamePasswordAuthenticationFilter中,果然发现了重写的attemptAuthentication登录逻辑,熟悉的AuthenticationToken也出现了。感觉这里才是应该做认证的地方,莫非我之前所有的自定义登录写法都是错误的? 线索逐渐清晰,但还有一个关键的点,即authenticationManager这个成员变量是哪里来的。虽然有一个以authenticationManager为参数的构造方法,但并没有地方调用,显然不是通过new的方式构造出来的。那只剩下一种可能了:它是通过父类AbstractAuthenticationProcessingFilter的setAuthenticationManager方法注入进去的,那我们就以这个为关键点继续查找。


AbstractAuthenticationFilterConfigurer

查找setAuthenticationManager方法,发现在外部只有一处调用,点开看看。 来到了第三个没见过的抽象类AbstractAuthenticationFilterConfigurer中。这里一目了然,认证需要的三个实体都有了:AuthenticationManager、SuccessHandler、FailHandler。而这个类,又是我们一开始看到的FormLoginConfigurer的父类。

  1. UsernamePasswordAuthenticationFilter这是用户名密码模式的登录拦截器,他继承自AbstractAuthenticationProcessingFilter这个抽象类,且实现了里面的attemptAuthentication()认证方法;
  2. FormLoginConfigurer创建了一个UsernamePasswordAuthenticationFilter实例,并且继承自AbstractAuthenticationFilterConfigurer,后者继续继承自AbstractHttpConfigurer,又继承了SecurityConfigurerAdapter,里面包含了一些初始化的抽象方法,重点来了! 其中有一个configure(B builder)可以获得HttpSecurity对象。

看看这个关系图,是不是觉得自己不配写Java。。。。。。

到这里,我们可以大胆地猜测一下,SpringSecurity的设计思路也许并不是 登陆接口放白名单 -> 请求 -> token拦截器 -> 接口 -> 不同登录方式走不同业务 -> 返回 这样,而是每一种登录模式走一套自己的拦截器链,而在这套拦截器链中,SpringSecurity为我们提供了Local AuthenticationManager,这一点可以从AbstractAuthenticationFilterConfigurer的子类中发现端倪。 为什么会这么说,当你多看几个AbstractAuthenticationFilterConfigurer的子类时,这种感觉会越来越强烈,比如下面的HttpBasicConfigurer。 同时,在常规的Filter、Token、Provider之外,还应该有一个Configurer类,专门用于创建Filter实例以及注入Local AuthenticationManager。

相关推荐
测开小菜鸟15 分钟前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity1 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天1 小时前
java的threadlocal为何内存泄漏
java
caridle1 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^2 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋32 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花2 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端2 小时前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan2 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源