在開發過程中,遇到問題,我們經常會使用搜索引擎來查找問題的解決方案,然后予以解決。但是有些問題一時半會搜索不到解決方案,需要自己去解決。這里分享下我解決這些問題使用的調試技巧,給大家一個解決問題的新思路!
問題描述
在《我扒了半天源碼,終于找到了Oauth2自定義處理結果的最佳方案!》一文中,當JWT令牌過期或者簽名不正確時,我們想要自定義網關認證失敗的返回結果。這個問題解決起來很簡單,只需修改一行代碼即可。但是當時查找解決方案確實花費了一番功夫,通過DEBUG源碼才找到了Spring Security中提供的自定義配置,解決了該問題。下面講講我是如何通過DEBUG源碼找到這個解決方案的!
解決過程
- 首先我們需要找到一個切入點,既然問題是由于JWT令牌過期或者簽名不正確才產生的,我們很容易想到RSASSAVerifier這個關鍵類,它的verify()方法是用來驗證簽名是否正確的,我們可以在該方法上面打個斷點DEBUG一下,發現程序執行過程果然會經過這里,要是簽名不正確會直接返回false;
- 這時候我們可以查下堆棧信息,了解下這次調用的整個過程,可以看到紅框以下的調用都是WebFlux里面的調用,沒有參考意義,所以調用最早是從NimbusReactiveJwtDecoder類開始的;
- 我們搜索下NimbusReactiveJwtDecoder在哪里被使用到了,可以找到又一個關鍵類ServerHttpSecurity,我們在網關的安全配置ResourceServerConfig中曾經用到過它,猜想下如果Spring Security提供了自定義配置,那估計就在這個類里面了;
- 查看下ServerHttpSecurity的類注釋,我們可以發現它相當于WebFlux版本的Spring Security配置;
/**
* A {@link ServerHttpSecurity} is similar to Spring Security's {@code HttpSecurity} but for WebFlux.
* It allows configuring web based security for specific http requests. By default it will be Applied
* to all requests, but can be restricted using {@link #securityMatcher(ServerWebExchangeMatcher)} or
* other similar methods.
**/
- 在我們網關的ResourceServerConfig中,我們曾經調用過ServerHttpSecurity的build()方法,用于生成SecurityWebFilterChain;
- 讓我們看看這個build()方法干了點啥,其中有段比較關鍵的是它調用了OAuth2ResourceServerSpec類的configure()方法;
- 而OAuth2ResourceServerSpec類的configure()方法又調用了JwtSpec類的configure()方法;
- 這個JwtSpec對象是不會為空的,因為我們在ResourceServerConfig中調用了OAuth2ResourceServerSpec類的jwt()方法創建了它;
- JwtSpec類的configure方法很關鍵,使用過濾器來進行認證是Spring Security實現認證的老套路了,于是我們找到了默認的認證過濾器BearerTokenAuthenticationWebFilter;
- BearerTokenAuthenticationWebFilter使用了OAuth2ResourceServerSpec中的entryPoint來處理認證失敗,默認實現為BearerTokenServerAuthenticationEntryPoint;
- 之后我們在BearerTokenAuthenticationWebFilter的filter()方法,BearerTokenServerAuthenticationEntryPoint的commence()方法上分別打個斷點,來驗證下,調用過程中都經過了,完全正確;
- 也就是說我們只要把默認的認證失敗處理器換成我們自定義的就行了,直接通過如下代碼把OAuth2ResourceServerSpec中的entryPoint來設置成自定義的即可。
/**
* 資源服務器配置
* Created by macro on 2020/6/19.
*/
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
//省略若干代碼...
//自定義處理JWT請求頭過期或簽名錯誤的結果
http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
//省略若干代碼...
return http.build();
}
}
總結
對于一時找不到解決方法的問題,我推薦使用DEBUG源碼的方式來解決。首先尋找一個突破口,可以從你熟悉的一些類中去尋找一個必定會執行的方法,然后打斷點,進行DEBUG,從調用的棧信息中查找出關鍵的類,之后通過這些關鍵類順藤摸瓜就能找解決方法了!






