文章目錄
一、 分析
在上面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
舉例,addFilterBefore
和 addFilterAfter
類似
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
比較器進行了比較。結果呼之欲出。
二、總結:
FilterComparator
比較器中初始化了Spring Security 自帶的Filter 的順序,即在創建時已經確定了默認Filter的順序。並將所有過濾器保存在一個filterToOrder
Map中。key值是Filter的類名,value是過濾器的順序號。- 當我們調用
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
之外的所有系統默認的攔截器之前。 - 最後Spring Security 會調用
HttpSecurity#performBuild
方法,在這裏會使用FilterComparator
比較器對filters
進行比較排序,序號小的在前,序號大的在後,序號相等則按照原先的filters中的順序。 - 由於在
filters
List集合中,我們自己添加的過濾器要在除了WebAsyncManagerIntegrationFilter
之外的所有系統默認的攔截器之前。導致了當我們調用了HttpSecurity#addFilterAt(A, B.class)
方法時,A攔截器要先於B攔截器執行。
舉例(爲了好理解,純屬假設):
- 假設Spring Security 裏面默認攔截器 有A、B、C 三個(在強調一遍,假設有這三個),那麼
FilterComparator
構造函數中就有這三個攔截器的順序值,假設Map值爲{“AcName” : 100, “BcName” : 200, “CcName” : 300}。其中AcName是A攔截器的類名,BC同理,100,200,300是他們的序號,用於確定順序。 - 我們添加一個攔截器 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) - 在 完成上述步驟後,
HttpSecurity
中也會把 F攔截器添加到一個待排序的List集合L中,然後在添加其他系統默認過濾器(相當於這個List保存了所有的過濾器,但是其調用順序未確定,還需要經過排序後才能確定)。最終List集合L就變成了{ A, F, B, C} (之前寫成了F,A,B,C了,經評論區指正,已修改),注意這個是未經排序的過濾器集合,排序後纔是真正的調用順序。
4.在調用HttpSecurity#performBuild
方法時,會將HttpSecurity
中的過濾器集合L進行排序,排序比較器就是FilterComparator
,排序規則就是誰的排序序號小誰在前,序號大的在後,序號相同的保持 L 中的順序。然後,得出最後的過濾器順序,也就是最終調用順序。
(講的略亂,我已經盡力了。。。)
以上:內容部分木有參考
如有侵擾,聯繫刪除。 內容僅用於自我記錄學習使用。如有錯誤,歡迎指正