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