Spring Security addFilter() 順序問題

一、 分析

在上面Spring Security + Jwt 項目的搭建過程中,我一直有一個問題。我們假設我們使用addFilterAt(A, B.class)。 即將A攔截器添加到B攔截器的位置。那麼addFilterAt 既然沒有覆蓋原先的攔截器,那麼A不是在B攔截器前面就是在B攔截器後面,那麼豈不是和addFilterBefore 或者 addFilterAfter 重複了?

然而元芳告訴我沒那麼簡單。最下面有總結,不想看源碼的小夥伴可以直接看總結。

通過斷點看到。我們通過添加攔截器的代碼是這個樣子的。
在這裏插入圖片描述
那麼我們看源碼:

0. FilterComparator

爲了更好的理解後面說的內容,我們需要先了解一下 FilterComparator 這個類。顧名思義,這個類是一個過濾器比較器。可以看到如下
在這裏插入圖片描述

其比較的規則如下, getOrder是根據類名來獲取到過濾器的序號。可以看到過濾器的排序是根據排序序號的大小來排序的,序號小的在前,大的災後。

	public int compare(Filter lhs, Filter rhs) {
		Integer left = getOrder(lhs.getClass());
		Integer right = getOrder(rhs.getClass());
		return left - right;
	}

我們這裏拿 addFilterAt 舉例,addFilterBeforeaddFilterAfter 類似

1. HttpSecurity#addFilterAt

在這裏插入圖片描述
一句一句看,先看第一句

1.1 this.comparator.registerAt(filter.getClass(), atFilter);

這裏的 方法是 在 FilterComparator#registerAt
這裏可以看到,當我們使用addFilterAt 添加過濾器時,他添加的排序序號是和我們指定的攔截器相同的。()

	
	public void registerAt(Class<? extends Filter> filter,
			Class<? extends Filter> atFilter) {
		// 獲取 atFilter 的順序
		Integer position = getOrder(atFilter);
		if (position == null) {
			throw new IllegalArgumentException(
					"Cannot register after unregistered Filter " + atFilter);
		}
		// 將filter放入 filterToOrder 中, filterToOrder 是一個map集合
		put(filter, position);
	}
	....
	private void put(Class<? extends Filter> filter, int position) {
		String className = filter.getName();
		filterToOrder.put(className, position);
	}
	// 調用  addFilterBefore 時使用這個方法註冊,這裏可以看到排序序號比指定過濾器要小1
	public void registerBefore(Class<? extends Filter> filter,
			Class<? extends Filter> beforeFilter) {
		Integer position = getOrder(beforeFilter);
		if (position == null) {
			throw new IllegalArgumentException(
					"Cannot register after unregistered Filter " + beforeFilter);
		}

		put(filter, position - 1);
	}
	// 調用  addFilterAfter 時使用這個方法註冊,這裏可以看到排序序號比指定過濾器要大1
	public void registerAfter(Class<? extends Filter> filter,
			Class<? extends Filter> afterFilter) {
		Integer position = getOrder(afterFilter);
		if (position == null) {
			throw new IllegalArgumentException(
					"Cannot register after unregistered Filter " + afterFilter);
		}

		put(filter, position + 1);
	}

簡單來說,就是我們 添加的 jwtLoginFilter 攔截器和 UsernamePasswordAuthenticationFilter 的序號是相同的。

1.2 HttpSecurity#addFilter(Filter filter)

代碼很簡單,如下,將添加的過濾器加到 filters 集合中

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

通過斷點我們還可以發現,我們手動添加的過濾器是先於大部分系統過濾器被加入到filters集合中的。這一點可以從斷點中看到
在這裏插入圖片描述

另外從 HttpSecurity#performBuild 方法中我們可以知道,filters 在添加完所有過濾器後,使用 FilterComparator 比較器進行了比較。結果呼之欲出。

二、總結:

  1. FilterComparator 比較器中初始化了Spring Security 自帶的Filter 的順序,即在創建時已經確定了默認Filter的順序。並將所有過濾器保存在一個 filterToOrder Map中。key值是Filter的類名,value是過濾器的順序號。
  2. 當我們調用 HttpSecurity#addFilterAt(A, B.class) 方法時(其中B一定是先於A添加,或者B本身就是默認的過濾器),他會將我們的添加的過濾器A在 FilterComparator ,並給給我們一個和B相同的序號(addFilterBefore(A, B.class) 給A的序號比B小1,addFilterAfter(A, B.class) 給A的序號比B大1)。同時,HttpSecurity#addFilter(Filter filter) 會將我們添加的過濾器添加在 filters List集合中, 而在List集合彙總我們手動添加的攔截器在除了 WebAsyncManagerIntegrationFilter 之外的所有系統默認的攔截器之前。
  3. 最後Spring Security 會調用HttpSecurity#performBuild 方法,在這裏會使用 FilterComparator 比較器對 filters進行比較排序,序號小的在前,序號大的在後,序號相等則按照原先的filters中的順序。
  4. 由於在 filters List集合中,我們自己添加的過濾器要在除了 WebAsyncManagerIntegrationFilter 之外的所有系統默認的攔截器之前。導致了當我們調用了 HttpSecurity#addFilterAt(A, B.class) 方法時,A攔截器要先於B攔截器執行。

舉例(爲了好理解,純屬假設):

  1. 假設Spring Security 裏面默認攔截器 有A、B、C 三個(在強調一遍,假設有這三個),那麼 FilterComparator 構造函數中就有這三個攔截器的順序值,假設Map值爲{“AcName” : 100, “BcName” : 200, “CcName” : 300}。其中AcName是A攔截器的類名,BC同理,100,200,300是他們的序號,用於確定順序。
  2. 我們添加一個攔截器 F 要調用addFilterAt(F, B.class)。那麼它首先會在 FilterComparator 中註冊F (保存在排序Map中),排序序號和B相同,也爲 200,這時候Map 就變成了{“AcName” : 100, “BcName” : 200, “CcName” : 300, “FcName” : 200}。(如果調用addFilterBefore F的序號就會減1,變成199; 如果調用addFilterAfter F的序號就會加1,變成201)
  3. 在 完成上述步驟後,HttpSecurity 中也會把 F攔截器添加到一個待排序的List集合L中,然後在添加其他系統默認過濾器(相當於這個List保存了所有的過濾器,但是其調用順序未確定,還需要經過排序後才能確定)。最終List集合L就變成了{ A, F, B, C} (之前寫成了F,A,B,C了,經評論區指正,已修改),注意這個是未經排序的過濾器集合,排序後纔是真正的調用順序。
    4.在調用 HttpSecurity#performBuild 方法時,會將HttpSecurity 中的過濾器集合L進行排序,排序比較器就是FilterComparator ,排序規則就是誰的排序序號小誰在前,序號大的在後,序號相同的保持 L 中的順序。然後,得出最後的過濾器順序,也就是最終調用順序。

(講的略亂,我已經盡力了。。。)

以上:內容部分木有參考
如有侵擾,聯繫刪除。 內容僅用於自我記錄學習使用。如有錯誤,歡迎指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章