HttpSecurity 也是 Spring Security 中的重要一環。我們平時所做的大部分 Spring Security 配置也都是基於 HttpSecurity 來配置的。因此我們有必要從原始碼的角度來理解下 HttpSecurity 到底幹了啥?
1.抽絲剝繭
首先我們來看下 HttpSecurity 的繼承關係圖:
可以看到,HttpSecurity 繼承自 AbstractConfiguredSecurityBuilder,同時實現了 SecurityBuilder 和 HttpSecurityBuilder 兩個介面。
我們來看下 HttpSecurity 的定義:
public final class HttpSecurity extends
AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity> {
//...
}
這裡每一個類都帶有泛型,看得人有點眼花繚亂。
我把這個泛型類拿出來和大家講一下,小夥伴們就明白了。
泛型主要是兩個,DefaultSecurityFilterChain 和 HttpSecurity,HttpSecurity 就不用說了,這是我們今天的主角,那麼 DefaultSecurityFilterChain 是幹嘛的?
這我們就得從 SecurityFilterChain 說起了。
1.1 SecurityFilterChain
先來看定義:
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
SecurityFilterChain 其實就是我們平時所說的 Spring Security 中的過濾器鏈,它裡邊定義了兩個方法,一個是 matches 方法用來匹配請求,另外一個 getFilters 方法返回一個 List 集合,集合中放著 Filter 物件,當一個請求到來時,用 matches 方法去比較請求是否和當前鏈吻合,如果吻合,就返回 getFilters 方法中的過濾器,那麼當前請求會逐個經過 List 集合中的過濾器。這一點,小夥伴們可以回憶前面【深入理解 FilterChainProxy【原始碼篇】】一文。
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 的這幾個父類捋一捋。
1.2 SecurityBuilder
public interface SecurityBuilder<O> {
O build() throws Exception;
}
SecurityBuilder 就是用來構建過濾器鏈的,在 HttpSecurity 實現 SecurityBuilder 時,傳入的泛型就是 DefaultSecurityFilterChain,所以 SecurityBuilder#build 方法的功能很明確,就是用來構建一個過濾器鏈出來。
1.3 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 中都將得到實現。
1.4 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 方法定義構建邏輯。
1.5 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。
2. 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 物件。
3.小結
好啦,這就是 HttpSecurity 的一個大致工作流程。把握住了這個工作流程,剩下的就只是一些簡單的重複的 xxxConfigurer 配置了,鬆哥就不再囉嗦啦。
如果小夥伴們覺得有收穫,記得點個在看鼓勵下鬆哥哦~