【Spring Security】—— WebSecurityConfigurerAdapter

官网地址 Spring Security Reference

版本:Version 5.5.0

WebSecurityConfigurerAdapter 关系图

Adapter 谷歌翻译:n. 【机】转接器 【网络】 适配器;适配器模式;接头。通过类名了解功能:我的理解,这个类是一个Web应用安全配置“接头”,及用户可通过这个“接头”接到自己的配置,也就是用户可以利用这个类来定制化安全配置。
【Spring Security】—— WebSecurityConfigurerAdapter_第1张图片

SecurityBuilder 接口

对这个接口暂时没有细看,先根据官网了解了一下这个接口想要实现的功能。不想扣的太细把自己绕进去,后面再回头细看。

public interface SecurityBuilder<O> {
    O build() throws Exception;
}
  • 接口功能描述:

    Interface for building an Object

    构建一个对象,泛型参数 O 代表将要构建的对象类型。

  • 待实现方法:

    O build();
    

    该接口只有这一个待实现的方法,该方法返回一个要构造的对象(类型为 O )或者 null。

    也就是说,SecurityBuilder的实现类的作用就是构建一个对象,至于怎么构建由具体实现完成,至于构造什么对象由传入的泛型决定。

SecurityConfigurer 接口

对这个接口暂时没有细看,先根据官网了解了一下这个接口想要实现的功能。不想扣的太细把自己绕进去,后面再回头细看。

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
    /**
     * 初始化 SecurityBuilder。
     * 这里只应该创建和修改共享状态,而不是用于构建对象的 SecurityBuilder 上的属性。 
     * 这里需要确保 configure(SecurityBuilder) 方法在执行时使用正确的共享对象。 
     */
    void init(B var1) throws Exception;
	
	/**
	 * 通过在 SecurityBuilder 上设置必要的属性来配置 SecurityBuilder。
	 */
    void configure(B var1) throws Exception;
}
  • 官网对这个接口的描述:

    Allows for configuring a SecurityBuilder. All SecurityConfigurer first have their init(SecurityBuilder) method invoked. After all init(SecurityBuilder) methods have been invoked, each configure(SecurityBuilder) method is invoked.
    Type Parameters:
    1. O - The object being built by the SecurityBuilder B
    2. B - The SecurityBuilder that builds objects of type O. This is also the SecurityBuilder that is being configured.

    翻译过来:SecurityConfigurer 可以配置 SecurityBuilder。 所有 SecurityConfigurer 首先调用它们的 init(SecurityBuilder) 方法。 在调用了所有 init(SecurityBuilder) 方法之后,将调用每个 configure(SecurityBuilder) 方法。
    类型上到泛型参数:
    1. O :SecurityBuilder B 正在构建的对象
    2. B :构建O对象实例的构建器,这个构建器正在被配置。

  • 个人理解:
    从官网的描述,大概可以推测:web容器中可以有多个 SecurityConfigurer ,每个 SecurityConfigurer 可以配置 SecurityBuilder,而 SecurityBuilder 的作用时构造一个对象,那么逻辑上感觉可能是这样的:
    【Spring Security】—— WebSecurityConfigurerAdapter_第2张图片
    SecurityConfigurer 和 SecurityBuilder 的最终目的时构造出一个对象 O ,而构造的过程由 SecurityBuilder 完成,SecurityConfigurer 可以配置SecurityBuilder 的具体构建细节。

第一眼看到SecurityConfigurer时产生了一个疑问:SecurityConfigurer> 这个结构看上去和 Map有点类似,那SecurityConfigurer是不是也可以实现成类似Map的结构,可以键值对应?

WebSecurityConfigurer 接口
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> 
						extends SecurityConfigurer<Filter, T> {

}

该接口继承了 SecurityConfigurer 接口,从源码中可以看出,它没有定义自己的方法,所有的方法都从父接口继承(init 和 configure 两个方法),那么他的作用本质上还是配置构造器SecurityBuilder,但是它对泛型做了约束:

这里涉及到的泛型:

  1. 将要输入WebSecurityConfigurer 的泛型 T
    T 继承了SecurityBuilder,所有 T 表示一个对象构建器(SecurityBuilder)
  2. 传入 SecurityBuilder 的泛型 Filter(javax.servlet.Filter)
    表示 SecurityBuilder 将要创建的对象是一个 javax.servlet.Filter ,也就是说,它约束了 T , 说明 T 的作用是构建 Filter
  3. 将要传入父接口SecurityConfigurer的两个泛型: Filter 和 T
    WebSecurityConfigurer 继承 SecurityConfigurer,从这里可以看出,对SecurityConfigurer做了约束,目前只有 T 还没有指定具体的类型,至于最终使用什么类型的构建器由实现类后者子接口器指定。

在SpringSecurity框架中,WebSecurityConfigurer 接口只提供了一个实现抽象类 WebSecurityConfigurerAdapter,该实现类指定的对象构建器是 WebSecurity,改对象构建器将来构建的对象是 Filter (过滤器):

public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
		 		implements SecurityBuilder<Filter>, ApplicationContextAware { " 省略源码 " }

WebSecurityConfigurerAdapter 抽象类

总体理解
public abstract class WebSecurityConfigurerAdapter implements
		WebSecurityConfigurer<WebSecurity> {"省略具体代码"}

官网描述:

Provides a convenient base class for creating a WebSecurityConfigurer instance. The implementation allows customization by overriding methods.
为创建 WebSecurityConfigurer 实例提供方便的基类。 该实现允许通过覆盖方法进行定制

这个类是我使用SpringSecurity框架时接触的第一个类,通过继承这个类,再覆盖其中的config方法,做一些定制,再加上@EnableWebSecurity注解,一个简单的安全认证服务就集成好了。
在这里插入图片描述
这是源码的另一部分注解,这部分我不知道怎么直接翻译,总觉得说出来意思有点绕。这一段的意思主要就是说:允许开发人员扩展默认的配置 allow developers to extend the defaults

  • 扩展方式:
    通过在 META-INF/spring.factories 文件中添加自己的配置,然后会由SpringFactoriesLoader 自动加载到内存,然后适配到SpringSecurity框架中。

    org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer #多个值用逗号隔开
    

    也就是说,我们通过继承 AbstractHttpConfigurer 定制化,然后配置到spring.factories,SpringSecurity框架会自动装配。

  • AbstractHttpConfigurer 是什么?
    一组安全配置器。这里我不打算详细的看这块代码,我怕自己被绕晕。我看来一下它的解释,它的目的是构建一个对HTTP请求进行安全控制的DefaultSecurityFilterChain。这样的化就很好理解了,也就是手我们可以自定义FilterChain来扩展默认的。

详细了解

通过idea工具可以看到 WebSecurityConfigurerAdapter 中都有些什么:

  • 四个内部类
    内部类
  • 一堆成员变量和方法,其中有两个构造方法,
    【Spring Security】—— WebSecurityConfigurerAdapter_第3张图片
内部类
  1. LazyPasswordEncoder
    处理密码的,比如对密码进行比对,对用户输入加密等
  2. UserDetailsServiceDelegator
    处理用户详细信息的服务代理,获取用户的详细信息的代理,里面有三种方式,内存中的用户信息,JDBC连接获取用户信息,serviceDao来查询用户信息
  3. AuthenticationManagerDelegator
    认证管理器代理类,利用认证管理器镜像用户认证
  4. DefaultPasswordEncoderAuthenticationManagerBuilder
    默认认证管理器构建器,构建认证管理器的
方法

先看构造方法。因为在看 SecurityConfigurer 接口时知道,它会先执行init方法初始化构建器,再执行config方法配置构建器,WebSecurityConfigurerAdapter 本质上就是实现 SecurityConfigurer 接口,所有我接下按这个顺序看源码。

  1. 构造方法
    在这里插入图片描述

    protected WebSecurityConfigurerAdapter() {
    	this(false);
    }
    
    protected WebSecurityConfigurerAdapter(boolean disableDefaults) {
    	this.disableDefaults = disableDefaults;
    }
    

    无参构造函数内部直接调用有参构造函数并传入false参数。disableDefaults 参数可以指定是否禁用默认配置,如果要禁用,必须对整改框架的实现足够了解。

  2. init 方法

    根据 SecurityConfigurer 接口中对init方法的描述,这里应该只是对 SecurityBuilder(在这里指的对象构建器就是WebSecurity,将要构建的对象就是Filter对象) 的一些状态进行了创建设置等,并没有进行正在的配置,配置工作最终交由config方法完成。

    //Initialize the SecurityBuilder.
    public void init(final WebSecurity web) throws Exception {
    	// 获取一个 HttpSecurity 对象实例
    	final HttpSecurity http = getHttp();
    	// 将 http 放入 web 并追加 postBuildAction
    	web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
    		public void run() {
    			FilterSecurityInterceptor securityInterceptor = http
    					.getSharedObject(FilterSecurityInterceptor.class);
    			web.securityInterceptor(securityInterceptor);
    		}
    	});
    }
    

    方法只有一个参数:WebSecurity 类型的一个对象(这与所实现接口中指定的哪个泛型是一致的,作用是用来构建Filter对象),这个参数对象被 final 修改,表示在方法执行过程中不可变(即不可以在方法中通过赋值的方式改变它),就是与所实现接口泛型指定的 WebSecurity 类型的一个对象。

    init方法中做了三件事:

    • 调用 getHttp 方法获得了一个 HttpSecurity 实例对象 http,获取到的这个实例被 final 修饰,也就是说 http 被创建出来之后就不可以再通过赋值的方式改变。

      点开 HttpSecurity 源码简单看来一下定义和注释,发现它也实现了 SecurityBuilder 接口,接口指定的泛型是 DefaultSecurityFilterChain ,那么它也是一个对象构建器,它可以根据具体的配置构建出一个过滤器链,最终对到达应用程序的http请求进行过滤,为服务提供 http 安全保护。

    • 将 HttpSecurity 放入 WebSecurity 中
      点开 addSecurityFilterChainBuilder 方法看了一下:

      private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();
      
      public WebSecurity addSecurityFilterChainBuilder( SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
      	this.securityFilterChainBuilders.add(securityFilterChainBuilder);
      	return this;
      }
      

      把刚才获取到的 http 添加到WebSecurity 的 securityFilterChainBuilders 属性中,securityFilterChainBuilders 本质上是一个 ArrayList 。这个属性在执行build的时候会用到。

    • 为WebSecurity 追加了一个postBuildAction
      postBuildAction 从字面猜测它的功能是在build之后的action,也就是在 WebSecurity 完成构建对象任务后立即执行的后置操作,这里的后置操作通过一个Runnable另启了一个线程来完成:在 build 都完成后从 http 中拿出 FilterSecurityInterceptor 类型的共享对象 并赋值给WebSecurity。

      FilterSecurityInterceptor 本质就是 Filter,这说明在 getHttp 方法中已经准备好了这个共享对象。

  3. configure 方法
    在这里插入图片描述

    // 默认方法实现中只是设置了 disableLocalConfigureAuthenticationBldr ,以判断怎么获取 AuthenticationManager
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    	this.disableLocalConfigureAuthenticationBldr = true;
    }
    
    public void configure(WebSecurity web) throws Exception {
    }
    
    protected void configure(HttpSecurity http) throws Exception {
    	logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
    
    	http
    		.authorizeRequests()
    			.anyRequest().authenticated()
    			.and()
    		.formLogin().and()
    		.httpBasic();
    }
    

    有三个 configure 方法:

    1. configure​(AuthenticationManagerBuilder auth)
      官网注释:由 authenticationManager() 的默认实现调用,以尝试获取 AuthenticationManager。
      点进 authenticationManager()方法看一下:

      	protected AuthenticationManager authenticationManager() throws Exception {
      		
      		if (!authenticationManagerInitialized) {
      			//调用 configure(AuthenticationManagerBuilder auth) 方法,默认情况下只是将disableLocalConfigureAuthenticationBldr 属性设置为 true
      			configure(localConfigureAuthenticationBldr);
      			// 根据 disableLocalConfigureAuthenticationBldr 的值选择获取 AuthenticationManager 的方式
      			if (disableLocalConfigureAuthenticationBldr) {
      				authenticationManager = authenticationConfiguration
      						.getAuthenticationManager();
      			} else {
      				authenticationManager = localConfigureAuthenticationBldr.build();
      			}
      			authenticationManagerInitialized = true;
      		}
      		return authenticationManager;
      	}
      

      看到这里就产生了疑问,authenticationConfiguration 和 localConfigureAuthenticationBldr 是哪里来的 ?这里涉及到了另外两个属性设置方法:

      // 自动注入的 AuthenticationConfiguration 
      @Autowired
      public void setAuthenticationConfiguration(
      		AuthenticationConfiguration authenticationConfiguration) {
      	this.authenticationConfiguration = authenticationConfiguration;
      }
      // 自动注入 ApplicationContext ,并在这里完成一些设置
      @Autowired
      public void setApplicationContext(ApplicationContext context) {
      	this.context = context;
      	// 从上下文中获取后置处理器
      	ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class);
      	LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);
      	// 设置 authenticationBuilder 
      	authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);
      	// 设置 localConfigureAuthenticationBldr 
      	localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {
      		@Override
      		public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
      			authenticationBuilder.eraseCredentials(eraseCredentials);
      			return super.eraseCredentials(eraseCredentials);
      		}
      	};
      }
      

      在这两个方法中设置了 authenticationConfiguration 和 localConfigureAuthenticationBldr,这里需要说明一下 authenticationConfiguration 的来源:
      默认情况下应该是框架默认会在上下文中设置一个默认的AuthenticationConfiguration类型的Bean,这样才能自动注入进来。如果不使用默认的,应该可以覆盖这个config方法配置AuthenticationManagerBuilder 构建器,然后在authenticationManager()调用这个config方法时,就可以通过定制的AuthenticationManagerBuilder来构建 AuthenticationConfiguration。

      authenticationManager()方法回在 getHttp()方法中被触发。源码注解中也给出了覆盖这个方法的例子:
      【Spring Security】—— WebSecurityConfigurerAdapter_第4张图片

    2. configure​(WebSecurity web)
      覆盖此方法以配置 WebSecurity。默认是一个空的方法体。这个方法是一个pubilc方法。

    3. configure​(HttpSecurity http)
      覆盖此方法以配置 HttpSecurity。默认情况下会追加一些 http 配置。

  4. getHttp 方法

    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected final HttpSecurity getHttp() throws Exception {
    	if (http != null) {
    		return http;
    	}
    
    	DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
    			.postProcess(new DefaultAuthenticationEventPublisher());
    	localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
    	// 调用 authenticationManager() 方法,试图获取 AuthenticationManager 
    	AuthenticationManager authenticationManager = authenticationManager();
    	authenticationBuilder.parentAuthenticationManager(authenticationManager);
    	authenticationBuilder.authenticationEventPublisher(eventPublisher);
    	// 共享对象,类是缓存的功能,存储配置中共享数据
    	Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
    	// 创建 HttpSecurity 对象,将准备好的配置都放进去
    	http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
    			sharedObjects);
    	// 调用默认构造方法创建WebSecurityConfigurerAdapter时,默认http配置是开启的,即默认 disableDefaults 为 false
    	if (!disableDefaults) {
    		// @formatter:off
    		http
    			.csrf().and()
    			.addFilter(new WebAsyncManagerIntegrationFilter())
    			.exceptionHandling().and()
    			.headers().and()
    			.sessionManagement().and()
    			.securityContext().and()
    			.requestCache().and()
    			.anonymous().and()
    			.servletApi().and()
    			.apply(new DefaultLoginPageConfigurer<>()).and()
    			.logout();
    		// @formatter:on
    		// 获取类加载器
    		ClassLoader classLoader = this.context.getClassLoader();
    		/**
    		 * 根据 spring.factories 文件中配置的 AbstractHttpConfigurer (安全配置器)
    		 * 这个以之前官网中所提到的扩展方式相对应
    		 */
    		List<AbstractHttpConfigurer> defaultHttpConfigurers = 
    				SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
    		/**
    		 * 将加载的 AbstractHttpConfigurer 适配到 HttpSecurity 构建器的 configurers 中
    		 * 用途就是 HttpSecurity 会用这些配置来构建 Filter
    		 * 		HttpSecurity 构建器的 configurers 是一个集合,用来存储配置对象的
    		 */
    		for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
    			http.apply(configurer);
    		}
    	}
    	// 调用 configure(HttpSecurity http) 方法,追加自定义配置
    	configure(http);
    	return http;
    }
    

    getHttp 方法在 init 方法中就被触发了,为后面构建对象做准备。这里只是大概看来已选http.apply方法,关于 HttpSecurity 中的细节后续在详细了解。

  5. authenticationManagerBean方法

    Override this method to expose the AuthenticationManager from configure(AuthenticationManagerBuilder) to be exposed as a Bean.
    覆盖此方法将 AuthenticationManager 对象作为一个Bean 公开,从 configure(AuthenticationManagerBuilder)方法 。

    public AuthenticationManager authenticationManagerBean() throws Exception {
    	return new AuthenticationManagerDelegator(authenticationBuilder, context);
    }
    

    这个方法在官网描述的功能是:将一个 AuthenticationManager 对象以作为 Bean 公开。这样就可以在继承WebSecurityConfigurerAdapter的配置类中通过重写这个方法并将返回值公开为 Bean,注入我们自己实现的 AuthenticationManager ,官方的例子:
    【Spring Security】—— WebSecurityConfigurerAdapter_第5张图片

  6. 剩余其他方法
    剩下的方法是一些属性配置的方法,可以对适配器进行属性的 set 配置。还有关于用户详细信息服务(UserDetailsService)的方法,也比较好理解: 【Spring Security】—— WebSecurityConfigurerAdapter_第6张图片

你可能感兴趣的:(Spring,Security,spring)