記兩種讓 Spring Security「少管閑事」的方法。
一個應用對外提供 Rest 接口,接口的通路認證通過 Spring Security OAuth2 控制,token 形式為 JWT。因為一些原因,某一特定路徑字首(假設為 <code>/custom/</code>)的接口需要使用另外一種自定義的認證方式,token 是一串無規則的随機字元串。兩種認證方式的 token 都是在 Headers 裡傳遞,形式都是 <code>Authorization: bearer xxx</code>。
是以當外部請求這個應用的接口時,情況示意如下:

這時,問題出現了。
我通過 <code>WebSecurityConfigurerAdapter</code> 配置 Spring Security 将 <code>/custom/</code> 字首的請求直接放行:
但請求 <code>/custom/</code> 字首的接口仍然被攔截,報了如下錯誤:
從錯誤提示首先可以通過檢查排除掉 <code>CustomWebFilter</code> 的嫌疑,自定義認證方式的 token 不是 JSON 格式,它裡面自然也不然嘗試去将其轉換成 JSON。
那推測問題出在 Spring Security 「多管閑事」,攔截了不該攔截的請求上。
經過一番面向搜尋程式設計和源碼調試,找到抛出以上錯誤資訊的位置是在 <code>JwtAccessTokenConverter.decode</code> 方法裡:
調用堆棧如下:
從調用的上下文可以看出(高亮那一行),執行邏輯在一個名為 <code>OAuth2AuthenticationProcessingFilter</code> 的 Filter 裡,會嘗試從請求中提取 Bearer Token,然後做一些處理(此處是 JWT 轉換和校驗等)。這個 Filter 是 <code>ResourceServerSecurityConfigurer.configure</code> 中初始化的,我們的應用同時也是作為一個 Spring Security OAuth2 Resource Server,從類名可以看出是對此的配置。
找到了問題所在之後,經過自己的思考和同僚間的讨論,得出了兩種可行的解決方案。
這個方案的思路是通過 AOP,在 <code>OAuth2AuthenticationProcessingFilter.doFilter</code> 方法執行前做個判斷
如果請求路徑是以 <code>/custom/</code> 開頭,就跳過該 Filter 繼續往後執行;
如果請求路徑非 <code>/custom/</code> 開頭,正常執行。
關鍵代碼示意:
如果能讓請求先到達我們自定義的 Filter,請求路徑以 <code>/custom/</code> 開頭的,處理完自定義 token 校驗等邏輯,然後将 <code>Authorization</code> Header 去掉(在 <code>OAuth2AuthenticationProcessingFilter.doFilter</code> 中,如果取不到 Bearer Token,不會抛異常),其它請求直接放行,也是一個可以達成目标的思路。
但現狀是自定義的 Filter 預設是在 <code>OAuth2AuthenticationProcessingFilter</code> 後執行的,如何實作它們的執行順序調整呢?
在我們前面找到的 <code>OAuth2AuthenticationProcessingFilter</code> 注冊的地方,也就是 <code>ResourceServerSecurityConfigurer.configure</code> 方法裡,我們可以看到 Filter 是通過以下這種寫法添加的:
核心方法是 <code>HttpSecurity.addFilterBefore</code>,說起 <code>HttpSecurity</code>,我們有印象啊……前面通過 <code>WebSecurityConfigurerAdapter</code> 來配置請求放行時入參是它,能否在那個時機将自定義 Filter 注冊到 <code>OAuth2AuthenticationProcessingFilter</code> 之前呢?
我們将前面配置放行規則處的代碼修改如下:
注: CustomWebFilter 改為直接 new 出來的,手動添加到 Security Filter Chain,不再自動注入到其它 Filter Chain。
為什麼是将自定義 Filter 添加到 <code>X509AuthenticationFilter.class</code> 之後呢?可以參考 spring-security-config 包的 <code>FilterComparator</code> 裡預置的 Filter 順序來做決定,從前面的代碼可知 <code>OAuth2AuthenticationProcessingFilter</code> 是添加到 <code>AbstractPreAuthenticatedProcessingFilter.class</code> 之前的,而在 <code>FilterComparator</code> 預置的順序裡,<code>X509AuthenticationFilter.class</code> 是在 <code>AbstractPreAuthenticatedProcessingFilter.class</code> 之前的,我們這樣添加就足以確定自定義 Filter 在 <code>OAuth2AuthenticationProcessingFilter</code> 之前。
做了以上修改,自定義 Filter 已經在我們預期的位置了,那麼我們在這個 Filter 裡面,對請求路徑以 <code>/custom/</code> 開頭的做必要處理,然後清空 <code>Authorization</code> Header 即可,關鍵代碼示意如下:
經過嘗試,兩種方案都能滿足需求,項目裡最終使用了方案一,相信也還有其它的思路可以解決問題。
經過這一過程,也暴露出了對 Spring Security 的了解不夠的問題,後續需要抽空做一些更深入的學習。
https://www.cnblogs.com/alalazy/p/13179608.html
更多原創技術内容,可以通過公衆号「悶騷的程式員」訂閱: