OAuth2.0實戰:認證、資源服務異常自定義!

bucaichenmou發表於2021-12-14

大家好,我是不才陳某~

這是《Spring Security 進階》的第4篇文章,往期文章如下:

文章都是成體系的,陳某預設看到這篇文章的讀者都已經看了前期的文章,之前的知識點就不再詳細介紹了。

今天這篇文章主要介紹一下實際工作中使用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 特定的異常,然後統一的進行異常處理,是不是這樣?

然後看一下上述的兩種異常屬於什麼型別的,如下:

UnsupportedGrantTypeException

InvalidGrantException

是不是都繼承了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,獲取方式如下:

  1. 《Spring Cloud 進階》PDF:關注公號:【碼猿技術專欄】回覆關鍵詞 Spring Cloud 進階 獲取!
  2. 《Spring Boot 進階》PDF:關注公號:【碼猿技術專欄】回覆關鍵詞 Spring Boot進階 獲取!
  3. 《Mybatis 進階》PDF:關注公號:【碼猿技術專欄】回覆關鍵詞 Mybatis 進階 獲取!

如果這篇文章對你有所幫助,或者有所啟發的話,幫忙點贊在看轉發收藏,你的支援就是我堅持下去的最大動力!

相關文章