Spring Security 實戰(zhàn)干貨:自定義異常處理

1. 前言

最近實在比較忙,很難抽出時間來繼續(xù)更 Spring Security 實戰(zhàn)干貨系列。今天正好項目中 Spring Security 需要對認證授權(quán)異常的處理,就分享出來吧 。

2. Spring Security 中的異常

Spring Security 中的異常主要分為兩大類:一類是認證異常,另一類是授權(quán)相關(guān)的異常。

2.1 AuthenticationException

AuthenticationException 是在用戶認證的時候出現(xiàn)錯誤時拋出的異常。主要的子類如圖:

AuthenticationException.png

根據(jù)該圖的信息,系統(tǒng)用戶不存在,被鎖定,憑證失效,密碼錯誤等認證過程中出現(xiàn)的異常都由 AuthenticationException 處理。

2.2 AccessDeniedException

AccessDeniedException 主要是在用戶在訪問受保護資源時被拒絕而拋出的異常。同 AuthenticationException 一樣它也提供了一些具體的子類。如下圖:

AccessDeniedException.png

AccessDeniedException 的子類比較少,主要是 CSRF 相關(guān)的異常和授權(quán)服務異常。

3. Http 狀態(tài)對認證授權(quán)的規(guī)定

Http 協(xié)議對認證授權(quán)的響應結(jié)果也有規(guī)定。

3.1 401 未授權(quán)狀態(tài)

HTTP 401 錯誤 - 未授權(quán)(Unauthorized) 一般來說該錯誤消息表明您首先需要登錄(輸入有效的用戶名和密碼)。 如果你剛剛輸入這些信息,立刻就看到一個 401 錯誤,就意味著,無論出于何種原因您的用戶名和密碼其中之一或兩者都無效(輸入有誤,用戶名暫時停用,賬戶被鎖定,憑證失效等) ??傊褪钦J證失敗了。其實正好對應我們上面的 AuthenticationException 。

3.2 403 被拒絕狀態(tài)

HTTP 403 錯誤 - 被禁止(Forbidden) 出現(xiàn)該錯誤表明您在訪問受限資源時沒有得到許可。服務器理解了本次請求但是拒絕執(zhí)行該任務,該請求不該重發(fā)給服務器。并且服務器想讓客戶端知道為什么沒有權(quán)限訪問特定的資源,服務器應該在返回的信息中描述拒絕的理由。一般實踐中我們會比較模糊的表明原因。 該錯誤對應了我們上面的 AccessDeniedException

4. Spring Security 中的異常處理

我們在 Spring Security 實戰(zhàn)干貨系列文章中的 自定義配置類入口 WebSecurityConfigurerAdapter 一文中提到 HttpSecurity 提供的 exceptionHandling() 方法用來提供異常處理。該方法構(gòu)造出 ExceptionHandlingConfigurer 異常處理配置類。該配置類提供了兩個實用接口:

  • AuthenticationEntryPoint 該類用來統(tǒng)一處理 AuthenticationException 異常
  • AccessDeniedHandler 該類用來統(tǒng)一處理 AccessDeniedException 異常

我們只要實現(xiàn)并配置這兩個異常處理類即可實現(xiàn)對 Spring Security 認證授權(quán)相關(guān)的異常進行統(tǒng)一的自定義處理。

4.1 實現(xiàn) AuthenticationEntryPoint

json 信息響應。

 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.http.MediaType;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.AuthenticationEntryPoint;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.HashMap;
 
 /**
  * @author dax
  * @since 2019/11/6 22:11
  */
 public class SimpleAuthenticationEntryPoint implements AuthenticationEntryPoint {
     @Override
     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
 
         //todo your business
         HashMap<String, String> map = new HashMap<>(2);
         map.put("uri", request.getRequestURI());
         map.put("msg", "認證失敗");
         response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
         response.setCharacterEncoding("utf-8");
         response.setContentType(MediaType.APPLICATION_JSON_VALUE);
         ObjectMapper objectMapper = new ObjectMapper();
         String resBody = objectMapper.writeValueAsString(map);
         PrintWriter printWriter = response.getWriter();
         printWriter.print(resBody);
         printWriter.flush();
         printWriter.close();
     }
 }

4.2 實現(xiàn) AccessDeniedHandler

同樣以 json 信息響應。

 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.http.MediaType;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.web.access.AccessDeniedHandler;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.HashMap;
 
 /**
  * @author dax
  * @since 2019/11/6 22:19
  */
 public class SimpleAccessDeniedHandler implements AccessDeniedHandler {
     @Override
     public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
         //todo your business
         HashMap<String, String> map = new HashMap<>(2);
         map.put("uri", request.getRequestURI());
         map.put("msg", "認證失敗");
         response.setStatus(HttpServletResponse.SC_FORBIDDEN);
         response.setCharacterEncoding("utf-8");
         response.setContentType(MediaType.APPLICATION_JSON_VALUE);
         ObjectMapper objectMapper = new ObjectMapper();
         String resBody = objectMapper.writeValueAsString(map);
         PrintWriter printWriter = response.getWriter();
         printWriter.print(resBody);
         printWriter.flush();
         printWriter.close();
     }
 }

4.3 個人實踐建議

其實我個人建議 Http 狀態(tài)碼 都返回 200 而將 401 狀態(tài)在 元信息 Map 中返回。因為異常狀態(tài)碼在瀏覽器端會以 error 顯示。我們只要能捕捉到 401403 就能認定是認證問題還是授權(quán)問題。

4.4 配置

實現(xiàn)了上述兩個接口后,我們只需要在 WebSecurityConfigurerAdapterconfigure(HttpSecurity http) 方法中配置即可。相關(guān)的配置片段如下:

 http.exceptionHandling().accessDeniedHandler(new SimpleAccessDeniedHandler()).authenticationEntryPoint(new SimpleAuthenticationEntryPoint())

5. 總結(jié)

今天我們對 Spring Security 中的異常處理進行了講解。分別實現(xiàn)了自定義的認證異常處理和自定義的授權(quán)異常處理。相關(guān)的 DEMO 可關(guān)注微信公眾號: Felordcn 回復 ss07 獲取。

關(guān)注公眾號:碼農(nóng)小胖哥,獲取更多資訊

個人博客:https://felord.cn

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容