深入浅出SpringSecurity和OAuth2(二)—— 安全过滤器FilterChainProxy

文章目录

    • 前言
    • 正文
      • 1. FilterChainProxy什么时候注入Spring容器中的
      • 2. WebSecurityConfiguration类
      • 3. WebSecurity类
      • 4. AbstractConfiguredSecurityBuilder类
      • 5. SecurityConfigurer类
      • 6. doBuild()方法
      • 7. WebSecurity什么时候被创建的?
    • 总结
    • 参考
    • 相关文章

前言

相信了解过SpringSecurity或者是OAuth2的读者,会发现网上会有非常多的相关文章,或是纯概念的,或是带有demo的,无论是哪种类型的文章,本人去阅读之后,对于整个框架的概念还是一知半解,也仅仅是实现了某些功能、某些效果而已,若遇到某些问题时无从下手,只能去百度去Google。这是因为对于SpringSecurity和OAuth2的知识没有一个整体概念的把握,知识体系没有形成系统,遂决定写一个关于SpringSecurity和OAuth2的系列专栏,在建造自己知识体系的同时还希望能帮助有同样困惑的同学。

本专栏希望通过通俗易懂的语言来达到从入门概念、demo开始,由简入深,最终能达到深谙其底层源码的目的。

============== 分割线 ==============

上篇文章粗略的讲解了SpringSecurity里的几个核心概念,本篇文章就来深扒一下安全过滤器FilterChainProxy的底层原理。

正文

1. FilterChainProxy什么时候注入Spring容器中的

在SpringSecurity中,FilterChainProxy是作为整个框架的核心,它是一个过滤器。在SpringSecurity中,一个过滤器名字叫springSecurityFilterChain,它的类型就是FilterChainProxy。

在@EnableWebSecurity注解中,@Import了一个关键类:WebSecurityConfiguration。

首先,WebSecurityConfiguration实现了ImportAware和BeanClassLoaderAware接口,分别实现了setImportMetadata()和setBeanClassLoader()

setImportMetadata()方法的作用是注入注解元数据。

	public void setImportMetadata(AnnotationMetadata importMetadata) {
		// 从注入的importMetadata中获取@EnableWebSecurity注解map值
		Map enableWebSecurityAttrMap = importMetadata
				.getAnnotationAttributes(EnableWebSecurity.class.getName());
		// 将map值封装成AnnotationAttributes
		AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes
				.fromMap(enableWebSecurityAttrMap);
		// 从AnnotationAttributes中获取debug属性值
		debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
		if (webSecurity != null) {
			// 将debugEnabled存到WebSecurity属性中
			webSecurity.debug(debugEnabled);
		}
	}

setBeanClassLoader方法作用就是注入类加载器ClassLoader。

	public void setBeanClassLoader(ClassLoader classLoader) {
		this.beanClassLoader = classLoader;
	}

废话不多说,看下FilterChainProxy是什么时候注入到Spring容器中的。

答案就在WebSecurityConfiguration的springSecurityFilterChain()方法中

	// AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME的值是:springSecurityFilterChain
	// 所以springSecurityFilterChain()的作用就是想Spring容器中注入一个名为springSecurityChain的bean。
	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		// 判断是否有webSecurityConfigurers
		boolean hasConfigurers = webSecurityConfigurers != null
				&& !webSecurityConfigurers.isEmpty();
		// 如果没有配置,则通过objectObjectPostProcessor来生成一个新的WebSecurityConfigurerAdapter		
		if (!hasConfigurers) {
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			webSecurity.apply(adapter);
		}
		return webSecurity.build();
	}

2. WebSecurityConfiguration类

在深入springSecurityFilterChain()方法底层原理之前,需要先了解WebSecurityConfiguration中几个重要的成员变量。

@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {

	// WebSecurity是用于建造FilterChainProxy过滤器的核心类
	private WebSecurity webSecurity;

	// 读取@EnableWebSecurity中的注解属性debugEnabled的值
	private Boolean debugEnabled;
	
	// SecurityConfigurer属性值集合
	private List> webSecurityConfigurers;
	
	// 类加载器
	private ClassLoader beanClassLoader;
	
	// 省略
}

3. WebSecurity类

第一个重要的类就是WebSecurity。

由WebSecurity的类注释可以了解到:

  • WebSecurity是由WebSecurityConfiguration创建的,用于创建FilterChainProxy
  • FilterChainProxy在SpringSecurity中,以springSecurityFilterChainProxy的bean名称暴露于容器之中的
  • 可以通过基础抽象类WebSecurityConfigurerAdapter来定制化WebSecurity

由于@EnableWebSecurity导入了WebSecurityConfiguration,因而WebSecurity也被创建。

我们先来看下WebSecurity的类结构:

public final class WebSecurity extends
		AbstractConfiguredSecurityBuilder implements
		SecurityBuilder, ApplicationContextAware {
		// 省略
}

首先,WebSecurity是一个final修饰的类,即该类:

  • 不能被继承,没有子类
  • 该类方法都为final

其次,WebSecurity继承了AbstractConfiguredSecurityBuilder。继续跟进看下AbstractConfiguredSecurityBuilder这个类有什么作用?

4. AbstractConfiguredSecurityBuilder类

由其类名:AbstractConfiguredSecurityBuilder就可以知道,该类是一个抽象类,作为抽象类,必然会抽象出abstract方法让子类去实现。浏览AbstractConfiguredSecurityBuilder的方法定义,可以看到它内部只定义了一个抽象方法:

protected abstract O performBuild() throws Exception;

这个方法会在建造FilterChainProxy时有使用到,这里先留个印象。

回到AbstractConfiguredSecurityBuilder类定义

public abstract class AbstractConfiguredSecurityBuilder>
		extends AbstractSecurityBuilder {
	// 省略
}		

可以知道它继承了AbstractSecurityBuilder,但自身带的一个泛型让人捉摸不透是啥意思。O是啥?B又是啥?

这里其实是用到了建造者模式

目光聚焦到SecurityBuilder这个接口

// 安全建造者
// 顾名思义是一个builder建造者,创建并返回一个类型为O的对象
public interface SecurityBuilder {
    O build() throws Exception;
}
// 抽象安全建造者
public abstract class AbstractSecurityBuilder implements SecurityBuilder {
    private AtomicBoolean building = new AtomicBoolean();
    private O object;

	// build建造方法被final修饰,不允许子类覆盖。
    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");
    }

    // 子类需要重写doBuild()方法
    protected abstract O doBuild() throws Exception;
}

所以B extends SecurityBuilder就是指B是SecurityBuilder的子类,用于建造O。

从WebSecurity中的类定义可以发现

AbstractConfiguredSecurityBuilder

AbstractConfiguredSecurityBuilder作用就是通过WebSecurity这个建造者建造出一个Filter,而这个Filter就是FilterChainProxy。

因此O就是指FilterChainProxy,B就是指的安全建造者WebSecurity。




再来看下AbstractConfiguredSecurityBuilder的成员变量。

public abstract class AbstractConfiguredSecurityBuilder>
		extends AbstractSecurityBuilder {
	private final Log logger = LogFactory.getLog(getClass());

	// 安全配置器的子类集合,这个configurers就是用于初始化以及配置FilterChainProxy中的filters用的
	private final LinkedHashMap>, List>> configurers = new LinkedHashMap<>();
	// 初始化期间添加的配置类
	private final List> configurersAddedInInitializing = new ArrayList<>();
	// 缓存
	private final Map, Object> sharedObjects = new HashMap<>();

	private final boolean allowConfigurersOfSameType;

	// 建造者的建造状态,初始状态为:未建造
	private BuildState buildState = BuildState.UNBUILT;

	// 用于生成bean
	private ObjectPostProcessor objectPostProcessor;
	// 省略
}
 
  

5. SecurityConfigurer类

SecurityConfigurer是一个接口,它就是指的安全配置器,看下它的类定义信息。

  • SecurityConfigurer允许用于配置安全建造者(SecurityBuilder)
  • 安全建造者(SecurityBuilder)在初始化时调用init方法,同时会初始化所有的SecurityConfigurer
  • 安全建造者(SecurityBuilder)在调用configure方法时,同时会调用所有SecurityConfigurer的configure
public interface SecurityConfigurer> {

	void init(B builder) throws Exception;

	void configure(B builder) throws Exception;
}

SecurityConfigurer 这个该怎么理解呢?

这里同上,O指的FilterChainProxy,B指的SecurityBuilder。

那么这里的SecurityConfigurer到底有什么作用呢?

答:SecurityConfigurer就是用于向FilterChainProxy中添加过滤器。



private final LinkedHashMap>, List>> configurers = new LinkedHashMap<>();

所以configurers就是以建造者为key,各种配置类为value的一个LinkedHashMap。

6. doBuild()方法

下面来看下AbstractConfiguredSecurityBuilder的doBuild()方法。doBuild()方法在之前提到过,它是AbstractSecurityBuilder抽象出来的方法,由AbstractConfiguredSecurityBuilder类来实现。这个方法会通过使用SecurityConfigurer去执行建造逻辑,建造逻辑主要分为五大步:

  1. 执行初始化前的所需做的额外工作(beforeInit())
  2. 执行初始化(init())
  3. 执行配置前所需的额外工作(beforeConfigure())
  4. 执行配置调用(configure)
  5. 执行建造(performBuild())
	@Override
	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			// 标记建造状态为初始化中
			buildState = BuildState.INITIALIZING;

			beforeInit();
			init();

			// 标记正在配置
			buildState = BuildState.CONFIGURING;

			beforeConfigure();
			configure();
			
			// 标记正在建造
			buildState = BuildState.BUILDING;

			O result = performBuild();
	
			// 标记以及建造完成
			buildState = BuildState.BUILT;

			return result;
		}
	}

performBuild()方法是AbstractConfiguredSecurityBuilder提供的抽象方法,具体有其子类:HttpSecurity和WebSecurity来实现。

7. WebSecurity什么时候被创建的?

答案就是在WebSecurityConfiguration的setFilterChainProxySecurityConfiguere()方法里。

	@Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor objectPostProcessor,
			@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List> webSecurityConfigurers)
			throws Exception {
			
		// 通过objectPostProcessor来创建一个WebSecurity,同时将objectPostProcessor传进WebSecurity对象中	
		webSecurity = objectPostProcessor
				.postProcess(new WebSecurity(objectPostProcessor));
		// 从@EnableWebSecurity中获取debugEnabled注解值,是否开启debug模式		
		if (debugEnabled != null) {
			webSecurity.debug(debugEnabled);
		}
		
		// 对webSecurityConfigurers进行排序
		webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);

		Integer previousOrder = null;
		Object previousConfig = null;

		for (SecurityConfigurer config : webSecurityConfigurers) {
			Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
			if (previousOrder != null && previousOrder.equals(order)) {
				throw new IllegalStateException(
						"@Order on WebSecurityConfigurers must be unique. Order of "
								+ order + " was already used on " + previousConfig + ", so it cannot be used on "
								+ config + " too.");
			}
			previousOrder = order;
			previousConfig = config;
		}

		// 遍历webSecurityConfigurers
		for (SecurityConfigurer webSecurityConfigurer : webSecurityConfigurers) {
			// 添加进WebSecurity中的配置器Map中
			webSecurity.apply(webSecurityConfigurer);
		}
		this.webSecurityConfigurers = webSecurityConfigurers;
	}
 
  

方法中的webSecurityConfigurers是通过了@Value注解来注入的bean集合,@Value表达式中又包含了一个autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()的调用。

进入AutowiredWebSecurityConfigurersIgnoreParents类,查看其方法getWebSecurityConfigurers()方法。

	public List> getWebSecurityConfigurers() {
		// 初始化webSecurityConfigurers集合
		List> webSecurityConfigurers = new ArrayList<>();
		// 通过beanFactory来获取所有注入Spring容器中的WebSecurityConfigurer子类,实际就是WebSecurityConfigurer
		Map beansOfType = beanFactory
				.getBeansOfType(WebSecurityConfigurer.class);
		for (Entry entry : beansOfType.entrySet()) {
			// 遍历出bean对象
			webSecurityConfigurers.add(entry.getValue());
		}
		return webSecurityConfigurers;
	}

总结

全文讲解了FilterChainProxy以及WebSecurityConfiguration,但是对于doBuild()方法没有进行细致的分析讲解,由于doBuild()的逻辑比较复杂,将在下一章进行细致分析。

在WebSecurityConfiguration中,需要关注两个方法:

  1. springSecurityFilterChain()方法
  2. setFilterChainProxySecurityConfigurer()方法

springSecurityFilterChain方法用于通过安全建造器WebSecurity来建造出FilterChainProxy。

setFilterChainProxySecurityConfigurer()方法是用于创建出安全建造起WebSecurity。

在SpringSecurity中,对于WebSecurity,使用了建造者设计模式。

作者本人github中开发并维护着一个后端基于Dubbo+SpringBoot+SpringSecurity+OAuth2而前端基于Vue的前后端分离电商项目,其中还维护着一个实战专栏用于详细记录开发该电商项目时遇到的技术难点以及解决方案,感兴趣读者可以前往查看,后端地址如下:
https://github.com/coderbruis/Distributed-mall
另外,本篇博文已收录至:https://github.com/coderbruis/JavaSourceCodeLearning

参考

  • 深入浅出Spring Security(一):三句话解释框架原理

相关文章

  • 从零开始系统学习SpringSecurity和OAuth2(一)—— 初识SpringSecurity

你可能感兴趣的:(SpringSecurity,OAuth2,原理,源码解读)