大家好,我是不才陳某~
這是《Spring Security 進階》的第4篇文章,往期文章如下:
- 實戰!Spring Boot Security+JWT前后端分離架構登錄認證!
- 妹子始終沒搞懂OAuth2.0,今天整合Spring Cloud Security 一次說明白!
- OAuth2.0實戰!使用JWT令牌認證!
文章都是成體系的,陳某默認看到這篇文章的讀者都已經看了前期的文章,之前的知識點就不再詳細介紹了。
今天這篇文章主要介紹一下實際工作中使用Spring Security需要定制的一些異常信息。
文章目錄如下:
案例服務搭建
此篇文章沿用上篇文章的認證、資源服務,如下:
1、認證服務oauth2-auth-server-jwt
2、資源服務oauth2-auth-resource-jwt
案例源碼已經上傳GitHub,關注公號:碼猿技術專欄,回復關鍵詞:9529 獲取!
認證服務的異常
先來看一下正確的獲取令牌的請求,以密碼模式為例,如下圖:
密碼模式需要傳遞5個參數,分別是用戶名、密碼、客戶端id,客戶端秘鑰、授權類型。
那么問題來了:如果任意一個參數傳錯了,返回什么?
1、用戶名、密碼錯誤
故意輸錯用戶名或者密碼,返回信息如下:
2、授權類型錯誤
輸入一個不存在的授權類型,返回信息如下:
3、客戶端ID,秘鑰錯誤
輸入錯誤的客戶端id或者秘鑰,返回信息如下:
感覺如何?是不是很酸爽?很顯然這返回的信息不適合前后端交互,別着急,下面介紹解決方案
認證服務自定義異常信息
上面列舉了三種常見的異常,解決方案實際可以分為兩種:
- 用戶名,密碼錯誤異常、授權類型異常
- 客戶端ID、秘鑰異常
陳某這里針對這兩種異常先上解決方案,后面再從源碼解釋為什么這么做?
1、用戶名,密碼錯誤異常、授權類型異常
針對用戶名、密碼、授權類型錯誤的異常解決方式比較復雜,需要定制的比較多。
1、定制提示信息、響應碼
這部分根據自己業務需要定制,陳某這里只是給出個例子,代碼如下:
2、自定義WebResponseExceptionTranslator
需要自定義一個異常翻譯器,默認的是DefaultWebResponseExceptionTranslator,此處必須重寫,其中有一個需要實現的方法,如下:
ResponseEntity<T> translate(Exception e) throws Exception;
這個方法就是根據傳遞過來的Exception判斷不同的異常返回特定的信息,這里需要判斷的異常的如下:
- UnsupportedGrantTypeException:不支持的授權類型異常
- InvalidGrantException:用戶名或者密碼錯誤的異常
創建一個OAuthServerWebResponseExceptionTranslator實現WebResponseExceptionTranslator,代碼如下:
3、認證服務配置文件中配置
需要將自定義的異常翻譯器OAuthServerWebResponseExceptionTranslator在配置文件中配置,很簡單,一行代碼的事。
在AuthorizationServerConfig配置文件指定,代碼如下:
案例源碼已經上傳GitHub,關注公號:碼猿技術專欄,回復關鍵詞:9529 獲取!
4、測試
按照上述的配置完成后,測試下用戶名、密碼錯誤、授權類型錯誤是否能夠正確返回定制的提示信息,如下:
5、源碼追蹤
實踐有了,總該理解一下為什么這么做吧?下面從源碼的角度告訴你為什么要這么做?
我們知道獲取令牌的接口為 /oauth/token,這個接口定義在TokenEndpoint#postAccessToken()(POST請求)方法中,如下圖:
我們先不看其中的邏輯,平時我們寫接口的異常怎么處理?
一般都是通過 @ExceptionHandler 特定的異常,然后統一的進行異常處理,是不是這樣?
然后看一下上述的兩種異常屬於什么類型的,如下:
是不是都繼承了OAuth2Exception,那么嘗試在TokenEndpoint這個類中找找有沒有處理OAuth2Exception這個異常的處理器,果然找到了一個 handleException() 方法,如下:
可以看到,這里的異常翻譯器已經使用了我們自定義的OAuthServerWebResponseExceptionTranslator。可以看下默認的異常翻譯器是啥,代碼如下:
看到沒,就是這個DefaultWebResponseExceptionTranslator
問題又來了:為什么在配置文件中設置了OAuthServerWebResponseExceptionTranslator就會生效呢?
這個不得不看下 @EnableAuthorizationServer 這個注解了,源碼如下:
注入了這個AuthorizationServerEndpointsConfiguration配置類,其中注入了AuthorizationEndpoint這個bean,如下:
將自定義的異常翻譯器設置進入了AbstractEndpoint這個抽象類中,而TokenEndpoint正是繼承了這個抽象類,復用了其中的異常翻譯器,代碼如下:
哦了,問題解決了,學東西一定要知其所以然...............
2、客戶端ID、秘鑰異常
這部分比較復雜,想要理解還是需要些基礎的,解決這個異常的方案很多,陳某只是介紹其中一種,下面詳細介紹。
1、定制提示信息、響應碼
這部分根據自己業務需要定制,陳某這里只是給出個例子,代碼如下:
2、自定義AuthenticationEntryPoint
這個AuthenticationEntryPoint是不是很熟悉,前面的文章已經介紹過了,此處需要自定義來返回定制的提示信息。
創建OAuthServerAuthenticationEntryPoint,實現AuthenticationEntryPoint,重寫其中的方法,代碼如下:
3、改造ClientCredentialsTokenEndpointFilter
ClientCredentialsTokenEndpointFilter這個過濾器的主要作用就是校驗客戶端的ID、秘鑰,代碼如下:
有幾個重要的部分需要講一下,如下:
- 構造方法中需要傳入第2步自定義的 OAuthServerAuthenticationEntryPoint
- 重寫 getAuthenticationManager() 方法返回IOC中的AuthenticationManager
- 重寫afterPropertiesSet() 方法,用於自定義認證失敗、成功處理器,失敗處理器中調用OAuthServerAuthenticationEntryPoint進行異常提示信息返回
4、OAuth配置文件中指定過濾器
只需要將自定義的過濾器添加到AuthorizationServerSecurityConfigurer中,代碼如下:
第①部分是添加過濾器,其中authenticationEntryPoint使用的是第2步自定義的OAuthServerAuthenticationEntryPoint
第②部分一定要注意:一定要去掉這行代碼,具體原因源碼解釋。
案例源碼已經上傳GitHub,關注公號:碼猿技術專欄,回復關鍵詞:9529 獲取!
5、測試
直接輸入錯誤的秘鑰,結果如下:
6、源碼追蹤
1、OAuthServerAuthenticationEntryPoint在何時調用?
OAuthServerAuthenticationEntryPoint這個過濾器繼承了 AbstractAuthenticationProcessingFilter 這個抽象類,一切的邏輯都在 doFilter() 中,陳某簡化了其中的關鍵代碼如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
try {
//調用子類的attemptAuthentication方法,獲取參數並且認證
authResult = attemptAuthentication(request, response);
}
catch (InternalAuthenticationServiceException failed) {
//一旦認證異常,則調用unsuccessfulAuthentication方法,通過failureHandler處理
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
//一旦認證異常,則調用unsuccessfulAuthentication方法,通過failureHandler處理
unsuccessfulAuthentication(request, response, failed);
return;
}
//認證成功,則調用successHandler處理
successfulAuthentication(request, response, chain, authResult);
}
關鍵代碼在 unsuccessfulAuthentication() 這個方法中,代碼如下:
2、自定義的過濾器如何生效的?
這個就要看 AuthorizationServerSecurityConfigurer#configure() 這個方法了,其中有一段代碼如下:
這段代碼就是遍歷添加的過濾器將其添加到過濾器鏈中,在BasicAuthenticationFilter這個過濾器之前。
添加到security的過濾器鏈中,這個過濾器自然會生效了。
3、為什么不能加.allowFormAuthenticationForClients()?
還是在 AuthorizationServerSecurityConfigurer#configure() 這個方法中,一旦設置了 allowFormAuthenticationForClients 為true,則會創建 ClientCredentialsTokenEndpointFilter,此時自定義的自然失效了。
資源服務器的異常
從認證服務獲取到令牌之后去請求資源服務的資源,這里涉及到的異常主要有兩個,如下:
1、令牌失效
比如令牌不正確、過期,此時返回的異常提示如下:
2、權限不足
令牌的權限不足,比如 /admin 接口只允許 admin 角色訪問,此時返回的異常信息如下:
資源服務自定義異常信息
下面針對上述兩種異常分別定制異常提示信息,這個比認證服務定制簡單。
1、令牌失效
這個比較簡單,也是需要自定義AuthenticationEntryPoint。步驟如下:
1、自定義AuthenticationEntryPoint
這個和認證服務的客戶端異常類似,這里不再詳細說了,直接貼代碼,如下:
2、OAuth配置文件中配置
這個比較簡單,直接在配置文件中配置即可,代碼如下:
3、測試
此時拿着失效的令牌訪問資源服務,可以看到已經正常返回定制的提示信息了,如下:
源碼和認證服務的類似,自己斷點試試,還是很簡單的。
案例源碼已經上傳GitHub,關注公號:碼猿技術專欄,回復關鍵詞:9529 獲取!
2、權限不足
這個異常定制就更簡單了,陳某在第一篇文章:實戰!Spring Boot Security+JWT前后端分離架構登錄認證!介紹過,下面簡單的貼下代碼。
1、自定義AccessDeniedHandler
代碼如下:
2、OAuth配置文件中配置
和令牌失效的異常配置在同一個方法中,代碼如下:
3、測試
訪問 /admin 接口,此時的提示信息如下:
案例源碼已經上傳GitHub,關注公號:碼猿技術專欄,回復關鍵詞:9529 獲取!
最后說一句(別白嫖,求關注)
陳某每一篇文章都是精心輸出,已經寫了3個專欄,整理成PDF,獲取方式如下:
- 《Spring Cloud 進階》PDF:關注公號:【碼猿技術專欄】回復關鍵詞 Spring Cloud 進階 獲取!
- 《Spring Boot 進階》PDF:關注公號:【碼猿技術專欄】回復關鍵詞 Spring Boot進階 獲取!
- 《Mybatis 進階》PDF:關注公號:【碼猿技術專欄】回復關鍵詞 Mybatis 進階 獲取!
如果這篇文章對你有所幫助,或者有所啟發的話,幫忙點贊、在看、轉發、收藏,你的支持就是我堅持下去的最大動力!