SpringSecurity(八):過濾器鏈


HttpSecurity

HttpSecurity 也是 Spring Security 中的重要一環。我們平時所做的大部分 Spring Security 配置也都是基於 HttpSecurity 來配置的。因此我們有必要從源碼的角度來理解下 HttpSecurity 到底干了啥?

首先我們來看下 HttpSecurity 的繼承關系圖:

可以看到,HttpSecurity 繼承自 AbstractConfiguredSecurityBuilder,同時實現了 SecurityBuilder 和 HttpSecurityBuilder 兩個接口。

我們來看下 HttpSecurity 的定義:

public final class HttpSecurity extends
		AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
		implements SecurityBuilder<DefaultSecurityFilterChain>,
		HttpSecurityBuilder<HttpSecurity> {
        //...
}

這里每一個類都帶有泛型,看得人有點眼花繚亂。

我把這個泛型類拿出來和大家講一下,小伙伴們就明白了。

泛型主要是兩個,DefaultSecurityFilterChain 和 HttpSecurity,HttpSecurity 就不用說了,這是我們今天的主角,那么 DefaultSecurityFilterChain 是干嘛的?

這我們就得從 SecurityFilterChain 說起了。

SecurityFilterChain

先來看定義:

public interface SecurityFilterChain {
	boolean matches(HttpServletRequest request);
	List<Filter> getFilters();
}

SecurityFilterChain 其實就是我們平時所說的 Spring Security 中的過濾器鏈,它里邊定義了兩個方法,一個是 matches 方法用來匹配請求,另外一個 getFilters 方法返回一個 List 集合,集合中放着 Filter 對象,當一個請求到來時,用 matches 方法去比較請求是否和當前鏈吻合,如果吻合,就返回 getFilters 方法中的過濾器,那么當前請求會逐個經過 List 集合中的過濾器。

SecurityFilterChain 接口只有一個實現類,那就是 DefaultSecurityFilterChain:

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
	private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
	private final RequestMatcher requestMatcher;
	private final List<Filter> filters;

	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
		this(requestMatcher, Arrays.asList(filters));
	}

	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
		logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
		this.requestMatcher = requestMatcher;
		this.filters = new ArrayList<>(filters);
	}

	public RequestMatcher getRequestMatcher() {
		return requestMatcher;
	}

	public List<Filter> getFilters() {
		return filters;
	}

	public boolean matches(HttpServletRequest request) {
		return requestMatcher.matches(request);
	}

	@Override
	public String toString() {
		return "[ " + requestMatcher + ", " + filters + "]";
	}
}

DefaultSecurityFilterChain 只是對 SecurityFilterChain 中的方法進行了實現,並沒有特別值得說的地方,松哥也就不啰嗦了。

那么從上面的介紹中,大家可以看到,DefaultSecurityFilterChain 其實就相當於是 Spring Security 中的過濾器鏈,一個 DefaultSecurityFilterChain 代表一個過濾器鏈,如果系統中存在多個過濾器鏈,則會存在多個 DefaultSecurityFilterChain 對象。

接下來我們把 HttpSecurity 的這幾個父類捋一捋。

SecurityBuilder

public interface SecurityBuilder<O> {
	O build() throws Exception;
}

SecurityBuilder 就是用來構建過濾器鏈的,在 HttpSecurity 實現 SecurityBuilder 時,傳入的泛型就是 DefaultSecurityFilterChain,所以 SecurityBuilder#build 方法的功能很明確,就是用來構建一個過濾器鏈出來。

HttpSecurityBuilder

HttpSecurityBuilder 看名字就是用來構建 HttpSecurity 的。不過它也只是一個接口,具體的實現在 HttpSecurity 中,接口定義如下:

public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>> extends
		SecurityBuilder<DefaultSecurityFilterChain> {
	<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(
			Class<C> clazz);
	<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(
			Class<C> clazz);
	<C> void setSharedObject(Class<C> sharedType, C object);
	<C> C getSharedObject(Class<C> sharedType);
	H authenticationProvider(AuthenticationProvider authenticationProvider);
	H userDetailsService(UserDetailsService userDetailsService) throws Exception;
	H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);
	H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);
	H addFilter(Filter filter);
}

這里的方法比較簡單:

getConfigurer 獲取一個配置對象。Spring Security 過濾器鏈中的所有過濾器對象都是由 xxxConfigure 來進行配置的,這里就是獲取這個 xxxConfigure 對象。
removeConfigurer 移除一個配置對象。
setSharedObject/getSharedObject 配置/獲取由多個 SecurityConfigurer 共享的對象。
authenticationProvider 方法表示配置驗證器。
userDetailsService 配置數據源接口。
addFilterAfter 在某一個過濾器之前添加過濾器。
addFilterBefore 在某一個過濾器之后添加過濾器。
addFilter 添加一個過濾器,該過濾器必須是現有過濾器鏈中某一個過濾器或者其擴展。

這便是 HttpSecurityBuilder 中的功能,這些接口在 HttpSecurity 中都將得到實現。

AbstractSecurityBuilder

AbstractSecurityBuilder 類實現了 SecurityBuilder 接口,該類中主要做了一件事,就是確保整個構建只被構建一次。

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
	private AtomicBoolean building = new AtomicBoolean();
	private O object;
	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");
	}
	public final O getObject() {
		if (!this.building.get()) {
			throw new IllegalStateException("This object has not been built");
		}
		return this.object;
	}
	protected abstract O doBuild() throws Exception;
}

可以看到,這里重新定義了 build 方法,並設置 build 方法為 final 類型,無法被重寫,在 build 方法中,通過 AtomicBoolean 實現該方法只被調用一次。具體的構建邏輯則定義了新的抽象方法 doBuild,將來在實現類中通過 doBuild 方法定義構建邏輯。

AbstractConfiguredSecurityBuilder

AbstractSecurityBuilder 方法的實現類就是 AbstractConfiguredSecurityBuilder。

AbstractConfiguredSecurityBuilder 中所做的事情就比較多了,我們分別來看。

首先 AbstractConfiguredSecurityBuilder 中定義了一個枚舉類,將整個構建過程分為 5 種狀態,也可以理解為構建過程生命周期的五個階段,如下:

private enum BuildState {
	UNBUILT(0),
	INITIALIZING(1),
	CONFIGURING(2),
	BUILDING(3),
	BUILT(4);
	private final int order;
	BuildState(int order) {
		this.order = order;
	}
	public boolean isInitializing() {
		return INITIALIZING.order == order;
	}
	public boolean isConfigured() {
		return order >= CONFIGURING.order;
	}
}

五種狀態分別是 UNBUILT、INITIALIZING、CONFIGURING、BUILDING 以及 BUILT。另外還提供了兩個判斷方法,isInitializing 判斷是否正在初始化,isConfigured 表示是否已經配置完畢。

AbstractConfiguredSecurityBuilder 中的方法比較多,松哥在這里列出來兩個關鍵的方法和大家分析:

private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
	Assert.notNull(configurer, "configurer cannot be null");
	Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
			.getClass();
	synchronized (configurers) {
		if (buildState.isConfigured()) {
			throw new IllegalStateException("Cannot apply " + configurer
					+ " to already built object");
		}
		List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
				.get(clazz) : null;
		if (configs == null) {
			configs = new ArrayList<>(1);
		}
		configs.add(configurer);
		this.configurers.put(clazz, configs);
		if (buildState.isInitializing()) {
			this.configurersAddedInInitializing.add(configurer);
		}
	}
}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
	List<SecurityConfigurer<O, B>> result = new ArrayList<>();
	for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
		result.addAll(configs);
	}
	return result;
}

第一個就是這個 add 方法,這相當於是在收集所有的配置類。將所有的 xxxConfigure 收集起來存儲到 configurers 中,將來再統一初始化並配置,configurers 本身是一個 LinkedHashMap ,key 是配置類的 class,value 是一個集合,集合里邊放着 xxxConfigure 配置類。當需要對這些配置類進行集中配置的時候,會通過 getConfigurers 方法獲取配置類,這個獲取過程就是把 LinkedHashMap 中的 value 拿出來,放到一個集合中返回。

另一個方法就是 doBuild 方法。

@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;
	}
}
private void init() throws Exception {
	Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
	for (SecurityConfigurer<O, B> configurer : configurers) {
		configurer.init((B) this);
	}
	for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
		configurer.init((B) this);
	}
}
private void configure() throws Exception {
	Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
	for (SecurityConfigurer<O, B> configurer : configurers) {
		configurer.configure((B) this);
	}
}

在 AbstractSecurityBuilder 類中,過濾器的構建被轉移到 doBuild 方法上面了,不過在 AbstractSecurityBuilder 中只是定義了抽象的 doBuild 方法,具體的實現在 AbstractConfiguredSecurityBuilder。

doBuild 方法就是一邊更新狀態,進行進行初始化。

beforeInit 是一個預留方法,沒有任何實現。

init 方法就是找到所有的 xxxConfigure,挨個調用其 init 方法進行初始化。

beforeConfigure 是一個預留方法,沒有任何實現。

configure 方法就是找到所有的 xxxConfigure,挨個調用其 configure 方法進行配置。

最后則是 performBuild 方法,是真正的過濾器鏈構建方法,但是在 AbstractConfiguredSecurityBuilder 中 performBuild 方法只是一個抽象方法,具體的實現在 HttpSecurity 中。

這便是 HttpSecurity 所有父類、父接口的功能。

看完了父輩,接下來回到我們今天文章的主題,HttpSecurity。

HttpSecurity

HttpSecurity 做的事情,就是進行各種各樣的 xxxConfigurer 配置。

隨便舉幾例:

public CorsConfigurer<HttpSecurity> cors() throws Exception {
	return getOrApply(new CorsConfigurer<>());
}
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
	ApplicationContext context = getContext();
	return getOrApply(new CsrfConfigurer<>(context));
}
public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
	return getOrApply(new ExceptionHandlingConfigurer<>());
}

HttpSecurity 中有大量類似的方法,過濾器鏈中的過濾器就是這樣一個一個配置的。我就不一一介紹了。

每個配置方法的結尾都會來一句 getOrApply,這個是干嘛的?

private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
		C configurer) throws Exception {
	C existingConfig = (C) getConfigurer(configurer.getClass());
	if (existingConfig != null) {
		return existingConfig;
	}
	return apply(configurer);
}

getConfigurer 方法是在它的父類 AbstractConfiguredSecurityBuilder 中定義的,目的就是去查看當前這個 xxxConfigurer 是否已經配置過了。

如果當前 xxxConfigurer 已經配置過了,則直接返回,否則調用 apply 方法,這個 apply 方法最終會調用到 AbstractConfiguredSecurityBuilder#add 方法,將當前配置 configurer 收集起來。

HttpSecurity 中還有一個 addFilter 方法:

public HttpSecurity addFilter(Filter filter) {
	Class<? extends Filter> filterClass = filter.getClass();
	if (!comparator.isRegistered(filterClass)) {
		throw new IllegalArgumentException(
				"The Filter class "
						+ filterClass.getName()
						+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
	}
	this.filters.add(filter);
	return this;
}

這個 addFilter 方法的作用,主要是在各個 xxxConfigurer 進行配置的時候,會調用到這個方法,(xxxConfigurer 就是用來配置過濾器的),把 Filter 都添加到 fitlers 變量中。

最終在 HttpSecurity 的 performBuild 方法中,構建出來一個過濾器鏈:

@Override
protected DefaultSecurityFilterChain performBuild() {
	filters.sort(comparator);
	return new DefaultSecurityFilterChain(requestMatcher, filters);
}

先給過濾器排序,然后構造 DefaultSecurityFilterChain 對象。

FilterChainProxy

我們可以看到一個過濾器鏈Spring Security Filter 封裝着多個過濾器 Filter,多個過濾器鏈封裝在一個FilterChainProxy中

可以看到,Spring Security Filter 並不是直接嵌入到 Web Filter 中的,而是通過 FilterChainProxy 來統一管理 Spring Security Filter,FilterChainProxy 本身則通過 Spring 提供的 DelegatingFilterProxy 代理過濾器嵌入到 Web Filter 之中。

FilterChainProxy 中可以存在多個過濾器鏈,如下圖:

可以看到,當請求到達 FilterChainProxy 之后,FilterChainProxy 會根據請求的路徑,將請求轉發到不同的 Spring Security Filters 上面去,不同的 Spring Security Filters 對應了不同的過濾器,也就是不同的請求將經過不同的過濾器。

這是 FilterChainProxy 的一個大致功能,今天我們就從源碼上理解 FilterChainProxy 中這些功能到底是怎么實現的。

先把 FilterChainProxy 源碼亮出來,這個源碼比較上,我們一部分一部分來,先從它聲明的全局屬性上開始:

private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(
		".APPLIED");
private List<SecurityFilterChain> filterChains;
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
private HttpFirewall firewall = new StrictHttpFirewall();

FILTER_APPLIED 變量是一個標記,用來標記過濾器是否已經執行過了。這個標記在 Spring Security 中很常見,松哥這里就不多說了。
filterChains 是過濾器鏈,注意,這個是過濾器鏈,而不是一個個的過濾器。
filterChainValidator 是 FilterChainProxy 配置完成后的校驗方法,默認使用的 NullFilterChainValidator 實際上對應了一個空方法,也就是不做任何校驗。
firewall 我們在前面的文章中也介紹過,這里就不再贅述。

接下來我們來看一個過濾器中最重要的 doFilter 方法:

@Override
public void doFilter(ServletRequest request, ServletResponse response,
		FilterChain chain) throws IOException, ServletException {
	boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
	if (clearContext) {
		try {
			request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
			doFilterInternal(request, response, chain);
		}
		finally {
			SecurityContextHolder.clearContext();
			request.removeAttribute(FILTER_APPLIED);
		}
	}
	else {
		doFilterInternal(request, response, chain);
	}
}

在 doFilter 方法中,正常來說,clearContext 參數每次都是 true,於是每次都先給 request 標記上 FILTER_APPLIED 屬性,然后執行 doFilterInternal 方法去走過濾器,執行完畢后,最后在 finally 代碼塊中清除 SecurityContextHolder 中保存的用戶信息,同時移除 request 中的標記。

按着這個順序,我們來看 doFilterInternal 方法:

private void doFilterInternal(ServletRequest request, ServletResponse response,
		FilterChain chain) throws IOException, ServletException {
	FirewalledRequest fwRequest = firewall
			.getFirewalledRequest((HttpServletRequest) request);
	HttpServletResponse fwResponse = firewall
			.getFirewalledResponse((HttpServletResponse) response);
	List<Filter> filters = getFilters(fwRequest);
	if (filters == null || filters.size() == 0) {
		if (logger.isDebugEnabled()) {
			logger.debug(UrlUtils.buildRequestUrl(fwRequest)
					+ (filters == null ? " has no matching filters"
							: " has an empty filter list"));
		}
		fwRequest.reset();
		chain.doFilter(fwRequest, fwResponse);
		return;
	}
	VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
	vfc.doFilter(fwRequest, fwResponse);
}
private List<Filter> getFilters(HttpServletRequest request) {
	for (SecurityFilterChain chain : filterChains) {
		if (chain.matches(request)) {
			return chain.getFilters();
		}
	}
	return null;
}

1.首先將請求封裝為一個 FirewalledRequest 對象,在這個封裝的過程中,也會判斷請求是否合法。
2.調用 getFilters 方法找到過濾器鏈。該方法就是根據當前的請求,從 filterChains 中找到對應的過濾器鏈,然后由該過濾器鏈去處理請求
3.如果找出來的 filters 為 null,或者集合中沒有元素,那就是說明當前請求不需要經過過濾器。直接執行 chain.doFilter ,這個就又回到原生過濾器中去了。那么什么時候會發生這種情況呢?那就是針對項目中的靜態資源,如果我們配置了資源放行,如 web.ignoring().antMatchers("/hello");,那么當你請求 /hello 接口時就會走到這里來,也就是說這個不經過 Spring Security Filter。
4.如果查詢到的 filters 中是有值的,那么這個 filters 集合中存放的就是我們要經過的過濾器鏈了。此時它會構造出一個虛擬的過濾器鏈 VirtualFilterChain 出來,並執行其中的 doFilter 方法。

那么接下來我們就來看看 VirtualFilterChain

private static class VirtualFilterChain implements FilterChain {
	private final FilterChain originalChain;
	private final List<Filter> additionalFilters;
	private final FirewalledRequest firewalledRequest;
	private final int size;
	private int currentPosition = 0;
	private VirtualFilterChain(FirewalledRequest firewalledRequest,
			FilterChain chain, List<Filter> additionalFilters) {
		this.originalChain = chain;
		this.additionalFilters = additionalFilters;
		this.size = additionalFilters.size();
		this.firewalledRequest = firewalledRequest;
	}
	@Override
	public void doFilter(ServletRequest request, ServletResponse response)
			throws IOException, ServletException {
		if (currentPosition == size) {
			if (logger.isDebugEnabled()) {
				logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
						+ " reached end of additional filter chain; proceeding with original chain");
			}
			// Deactivate path stripping as we exit the security filter chain
			this.firewalledRequest.reset();
			originalChain.doFilter(request, response);
		}
		else {
			currentPosition++;
			Filter nextFilter = additionalFilters.get(currentPosition - 1);
			if (logger.isDebugEnabled()) {
				logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
						+ " at position " + currentPosition + " of " + size
						+ " in additional filter chain; firing Filter: '"
						+ nextFilter.getClass().getSimpleName() + "'");
			}
			nextFilter.doFilter(request, response, this);
		}
	}
}

1.VirtualFilterChain 類中首先聲明了 5 個全局屬性,originalChain 表示原生的過濾器鏈,也就是 Web Filter;additionalFilters 表示 Spring Security 中的過濾器鏈;firewalledRequest 表示當前請求;size 表示過濾器鏈中過濾器的個數;currentPosition 則是過濾器鏈遍歷時候的下標。
2.doFilter 方法就是 Spring Security 中過濾器挨個執行的過程,如果 currentPosition == size,表示過濾器鏈已經執行完畢,此時通過調用 originalChain.doFilter 進入到原生過濾鏈方法中,同時也退出了 Spring Security 過濾器鏈。否則就從 additionalFilters 取出 Spring Security 過濾器鏈中的一個個過濾器,挨個調用 doFilter 方法。

最后,FilterChainProxy 中還定義了 FilterChainValidator 接口及其實現:

public interface FilterChainValidator {
	void validate(FilterChainProxy filterChainProxy);
}
private static class NullFilterChainValidator implements FilterChainValidator {
	@Override
	public void validate(FilterChainProxy filterChainProxy) {
	}
}

實際上這個實現並未做任何事情。

這就是 FilterChainProxy 中的整個邏輯。

SecurityConfigurer

SecurityConfigurer在 Spring Security 中是一個非常重要的角色。在前面的文章中,松哥曾經多次提到過,Spring Security 過濾器鏈中的每一個過濾器,都是通過 xxxConfigurer 來進行配置的,而這些 xxxConfigurer 實際上都是 SecurityConfigurer 的實現。

所以我們今天有必要來跟大家把 SecurityConfigurer 從頭到尾捋一捋。

SecurityConfigurer

SecurityConfigurer 本身是一個接口,我們來看下:

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {

	void init(B builder) throws Exception;

	void configure(B builder) throws Exception;
}

可以看到,SecurityConfigurer 中主要是兩個方法,init 和 configure。

init 就是一個初始化方法。而 configure 則是一個配置方法。這里只是規范了方法的定義,具體的實現則在不同的實現類中。

需要注意的是這兩個方法的參數類型都是一個泛型 B,也就是 SecurityBuilder 的子類,關於 SecurityBuilder ,它是用來構建過濾器鏈的,松哥將在下篇文章中和大家介紹。

SecurityConfigurer 有三個實現類:

SecurityConfigurerAdapter
GlobalAuthenticationConfigurerAdapter
WebSecurityConfigurer
我們分別來看。

SecurityConfigurerAdapter

SecurityConfigurerAdapter 實現了 SecurityConfigurer 接口,我們所使用的大部分的 xxxConfigurer 也都是 SecurityConfigurerAdapter 的子類。

SecurityConfigurerAdapter 在 SecurityConfigurer 的基礎上,還擴展出來了幾個非常好用的方法,我們一起來看下:

public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>>
		implements SecurityConfigurer<O, B> {
	private B securityBuilder;

	private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();

	public void init(B builder) throws Exception {
	}

	public void configure(B builder) throws Exception {
	}

	public B and() {
		return getBuilder();
	}
	protected final B getBuilder() {
		if (securityBuilder == null) {
			throw new IllegalStateException("securityBuilder cannot be null");
		}
		return securityBuilder;
	}
	@SuppressWarnings("unchecked")
	protected <T> T postProcess(T object) {
		return (T) this.objectPostProcessor.postProcess(object);
	}
	public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
		this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
	}
	public void setBuilder(B builder) {
		this.securityBuilder = builder;
	}
	private static final class CompositeObjectPostProcessor implements
			ObjectPostProcessor<Object> {
		private List<ObjectPostProcessor<?>> postProcessors = new ArrayList<>();

		@SuppressWarnings({ "rawtypes", "unchecked" })
		public Object postProcess(Object object) {
			for (ObjectPostProcessor opp : postProcessors) {
				Class<?> oppClass = opp.getClass();
				Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass,
						ObjectPostProcessor.class);
				if (oppType == null || oppType.isAssignableFrom(object.getClass())) {
					object = opp.postProcess(object);
				}
			}
			return object;
		}
		private boolean addObjectPostProcessor(
				ObjectPostProcessor<?> objectPostProcessor) {
			boolean result = this.postProcessors.add(objectPostProcessor);
			postProcessors.sort(AnnotationAwareOrderComparator.INSTANCE);
			return result;
		}
	}
}

1.CompositeObjectPostProcessor 首先一開始聲明了一個 CompositeObjectPostProcessor 實例,CompositeObjectPostProcessor 是 ObjectPostProcessor 的一個實現,ObjectPostProcessor 本身是一個后置處理器,該后置處理器默認有兩個實現,AutowireBeanFactoryObjectPostProcessor 和 CompositeObjectPostProcessor。其中 AutowireBeanFactoryObjectPostProcessor 主要是利用了 AutowireCapableBeanFactory 對 Bean 進行手動注冊,因為在 Spring Security 中,很多對象都是手動 new 出來的,這些 new 出來的對象和容器沒有任何關系,利用 AutowireCapableBeanFactory 可以將這些手動 new 出來的對象注入到容器中,而 AutowireBeanFactoryObjectPostProcessor 的主要作用就是完成這件事;CompositeObjectPostProcessor 則是一個復合的對象處理器,里邊維護了一個 List 集合,這個 List 集合中,大部分情況下只存儲一條數據,那就是 AutowireBeanFactoryObjectPostProcessor,用來完成對象注入到容器的操作,如果用戶自己手動調用了 addObjectPostProcessor 方法,那么 CompositeObjectPostProcessor 集合中維護的數據就會多出來一條,在 CompositeObjectPostProcessor#postProcess 方法中,會遍歷集合中的所有 ObjectPostProcessor,挨個調用其 postProcess 方法對對象進行后置處理。
2.and 方法,該方法返回值是一個 securityBuilder,securityBuilder 實際上就是 HttpSecurity,我們在 HttpSecurity 中去配置不同的過濾器時,可以使用 and 方法進行鏈式配置,就是因為這里定義了 and 方法並返回了 securityBuilder 實例。

這便是 SecurityConfigurerAdapter 的主要功能,后面大部分的 xxxConfigurer 都是基於此類來實現的。

GlobalAuthenticationConfigurerAdapter

GlobalAuthenticationConfigurerAdapter 看名字就知道是一個跟全局配置有關的東西,它本身實現了 SecurityConfigurerAdapter 接口,但是並未對方法做具體的實現,只是將泛型具體化了:

@Order(100)
public abstract class GlobalAuthenticationConfigurerAdapter implements
		SecurityConfigurer<AuthenticationManager, AuthenticationManagerBuilder> {

	public void init(AuthenticationManagerBuilder auth) throws Exception {
	}

	public void configure(AuthenticationManagerBuilder auth) throws Exception {
	}
}

可以看到,SecurityConfigurer 中的泛型,現在明確成了 AuthenticationManager 和 AuthenticationManagerBuilder。所以 GlobalAuthenticationConfigurerAdapter 的實現類將來主要是和配置 AuthenticationManager 有關。當然也包括默認的用戶名密碼也是由它的實現類來進行配置的。

我們在 Spring Security 中使用的 AuthenticationManager 其實可以分為兩種,一種是局部的,另一種是全局的,這里主要是全局的配置。

WebSecurityConfigurer

還有一個實現類就是 WebSecurityConfigurer,這個可能有的小伙伴比較陌生,其實他就是我們天天用的 WebSecurityConfigurerAdapter 的父接口。

所以 WebSecurityConfigurer 的作用就很明確了,用戶擴展用戶自定義的配置。

SecurityConfigurer 默認主要是這三個實現,考慮到大多數的過濾器配置都是通過 SecurityConfigurerAdapter 進行擴展的,因此我們今天就通過這條線進行展開。

SecurityConfigurerAdapter

SecurityConfigurerAdapter 的實現主要也是三大類:

UserDetailsAwareConfigurer
AbstractHttpConfigurer
LdapAuthenticationProviderConfigurer

考慮到 LDAP 現在使用很少,所以這里我來和大家重點介紹下前兩個。

UserDetailsAwareConfigurer

這個配置類看名字大概就知道這是用來配置用戶類的

AbstractDaoAuthenticationConfigurer

AbstractDaoAuthenticationConfigurer 中所做的事情比較簡單,主要是構造了一個默認的 DaoAuthenticationProvider,並為其配置 PasswordEncoder 和 UserDetailsService。

UserDetailsServiceConfigurer

UserDetailsServiceConfigurer 重寫了 AbstractDaoAuthenticationConfigurer 中的 configure 方法,在 configure 方法執行之前加入了 initUserDetailsService 方法,以方便開發展按照自己的方式去初始化 UserDetailsService。不過這里的 initUserDetailsService 方法是空方法。

UserDetailsManagerConfigurer

UserDetailsManagerConfigurer 中實現了 UserDetailsServiceConfigurer 中定義的 initUserDetailsService 方法,具體的實現邏輯就是將 UserDetailsBuilder 所構建出來的 UserDetails 以及提前准備好的 UserDetails 中的用戶存儲到 UserDetailsService 中。

該類同時添加了 withUser 方法用來添加用戶,同時還增加了一個 UserDetailsBuilder 用來構建用戶,這些邏輯都比較簡單,小伙伴們可以自行查看。

JdbcUserDetailsManagerConfigurer

JdbcUserDetailsManagerConfigurer 在父類的基礎上補充了 DataSource 對象,同時還提供了相應的數據庫查詢方法。

InMemoryUserDetailsManagerConfigurer

InMemoryUserDetailsManagerConfigurer 在父類的基礎上重寫了構造方法,將父類中的 UserDetailsService 實例定義為 InMemoryUserDetailsManager。

DaoAuthenticationConfigurer

DaoAuthenticationConfigurer 繼承自 AbstractDaoAuthenticationConfigurer,只是在構造方法中修改了一下 userDetailsService 而已。

有小伙伴可能要問了,JdbcUserDetailsManagerConfigurer 或者 InMemoryUserDetailsManagerConfigurer,到底在哪里可以用到呀?

松哥給大家舉一個簡單的例子:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("javaboy")
                .password("{noop}123")
                .roles("admin");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                //省略
    }
}

當你調用 auth.inMemoryAuthentication 進行配置時,實際上調用的就是 InMemoryUserDetailsManagerConfigurer。

這下明白了吧!

AbstractHttpConfigurer

AbstractHttpConfigurer 這一派中的東西非常多,我們所有的過濾器配置,都是它的子類,我們來看下都有哪些類?

可以看到,它的實現類還是非常多的。

這么多實現類,松哥就不一一給大家介紹了,我挑一個常用的 FormLoginConfigurer 來給大家詳細介紹,只要大家把這個理解了,其他的照貓畫虎就很好理解了。

我們一個一個來看。

AbstractHttpConfigurer

AbstractHttpConfigurer 繼承自 SecurityConfigurerAdapter,並增加了兩個方法,disable 和 withObjectPostProcessor:

public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
		extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {

	/**
	 * Disables the {@link AbstractHttpConfigurer} by removing it. After doing so a fresh
	 * version of the configuration can be applied.
	 *
	 * @return the {@link HttpSecurityBuilder} for additional customizations
	 */
	@SuppressWarnings("unchecked")
	public B disable() {
		getBuilder().removeConfigurer(getClass());
		return getBuilder();
	}

	@SuppressWarnings("unchecked")
	public T withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
		addObjectPostProcessor(objectPostProcessor);
		return (T) this;
	}
}

這兩個方法松哥之前都有給大家介紹過,disable 基本上是大家的老熟人了,我們常用的 .csrf().disable() 就是出自這里,那么從這里我們也可以看到 disable 的實現原理,就是從 getBuilder 中移除相關的 xxxConfigurer,getBuilder 方法獲取到的實際上就是 HttpSecurity,所以移除掉 xxxConfigurer 實際上就是從過濾器鏈中移除掉某一個過濾器,例如 .csrf().disable() 就是移除掉處理 csrf 的過濾器。

另一個增加的方法是 withObjectPostProcessor,這是為配置類添加手動添加后置處理器的。在 AbstractHttpConfigurer 的父類中其實有一個類似的方法就是 addObjectPostProcessor,但是 addObjectPostProcessor 只是一個添加方法,返回值為 void,而 withObjectPostProcessor 的返回值是當前配置類,也就是 xxxConfigurer,所以如果使用 withObjectPostProcessor 的話,可以使用鏈式配置,事實上,在松哥之前的文章,以及 vhr(https://github.com/lenve/vhr) 項目中,使用的也都是 withObjectPostProcessor 方法(當然,你也可以使用 addObjectPostProcessor,最終效果是一樣的)。

AbstractAuthenticationFilterConfigurer

AbstractAuthenticationFilterConfigurer 類的功能比較多,源碼也是相當相當長。不過我們只需要抓住兩點即可,init 方法和 configure 方法,因為這兩個方法是所有 xxxConfigurer 的靈魂。

@Override
public void init(B http) throws Exception {
	updateAuthenticationDefaults();
	updateAccessDefaults(http);
	registerDefaultAuthenticationEntryPoint(http);
}

init 方法主要干了三件事:

updateAuthenticationDefaults 主要是配置了登錄處理地址,失敗跳轉地址,注銷成功跳轉地址。
updateAccessDefaults 方法主要是對 loginPage、loginProcessingUrl、failureUrl 進行 permitAll 設置(如果用戶配置了 permitAll 的話)。
registerDefaultAuthenticationEntryPoint 則是注冊異常的處理器。
再來看 configure 方法:

@Override
public void configure(B http) throws Exception {
	PortMapper portMapper = http.getSharedObject(PortMapper.class);
	if (portMapper != null) {
		authenticationEntryPoint.setPortMapper(portMapper);
	}
	RequestCache requestCache = http.getSharedObject(RequestCache.class);
	if (requestCache != null) {
		this.defaultSuccessHandler.setRequestCache(requestCache);
	}
	authFilter.setAuthenticationManager(http
			.getSharedObject(AuthenticationManager.class));
	authFilter.setAuthenticationSuccessHandler(successHandler);
	authFilter.setAuthenticationFailureHandler(failureHandler);
	if (authenticationDetailsSource != null) {
		authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
	}
	SessionAuthenticationStrategy sessionAuthenticationStrategy = http
			.getSharedObject(SessionAuthenticationStrategy.class);
	if (sessionAuthenticationStrategy != null) {
		authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
	}
	RememberMeServices rememberMeServices = http
			.getSharedObject(RememberMeServices.class);
	if (rememberMeServices != null) {
		authFilter.setRememberMeServices(rememberMeServices);
	}
	F filter = postProcess(authFilter);
	http.addFilter(filter);
}

configure 中的邏輯就很簡答了,構建各種各樣的回調函數設置給 authFilter,authFilter 再去 postProcess 中走一圈注冊到 Spring 容器中,最后再把 authFilter 添加到過濾器鏈中。

這便是 AbstractAuthenticationFilterConfigurer 的主要功能。需要提醒大家的是,我們日常配置的,如:

loginPage
loginProcessingUrl
permitAll
defaultSuccessUrl
failureUrl

等方法都是在這里定義的。

最后我們再來看看 FormLoginConfigurer。

FormLoginConfigurer

FormLoginConfigurer 在定義是,明確了 AbstractAuthenticationFilterConfigurer 中的泛型是 UsernamePasswordAuthenticationFilter,也就是我們這里最終要配置的過濾是 UsernamePasswordAuthenticationFilter。

FormLoginConfigurer 重寫了 init 方法,配置了一下默認的登錄頁面。其他的基本上都是從父類來的,未做太多改變。

另外我們日常配置的很多東西也是來自這里:

好啦,這就是 FormLoginConfigurer 這個配置類,FormLoginConfigurer 對應的過濾器是 UsernamePasswordAuthenticationFilter,小伙伴們可以自行分析其他的 xxxConfigurer,每一個 xxxConfigurer 都對應了一個 不同的 Filter。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM