大家好,我是不才陳某~
這是《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 進階 獲取!
如果這篇文章對你有所幫助,或者有所啟發的話,幫忙點贊、在看、轉發、收藏,你的支援就是我堅持下去的最大動力!