Spring Security 中自定義異常處理
我們最常見的 UsernamePasswordAuthenticationFilter 和 FilterSecurityInterceptor 這 2 個 Filter 在拋異常(和處理)的邏輯是不一樣的:
-
UsernamePasswordAuthenticationFilter 在做認證時,如果拋出了異常(例如認證不通過時),是它自己
try ... catch ...
處理掉了。 -
FilterSecurityInterceptor 在做鑒權時,如果拋出了異常(例如用戶未登錄或用戶權限不夠),它是將異常拋給了它前面的 ExceptionTranslationFilter ,由 ExceptionTranslationFilter 來
try ... catch ...
處理。
#1. 認證環節的異常處理
Spring Security 的認證工作是由 UsernamePasswordAuthenticationFilter 處理的。查看它( 和它的父類 AbstractAuthenticationProcessingFilter )的源碼,我們可以看到:
-
當認證通過時,會執行它( 繼承自父類 )的 successfulAuthentication 方法。successfulAuthentication 的默認行為(之前講過):繼續用戶原本像訪問的 URI 。
另外,你可以通過
http.successHandler(...)
來 “覆蓋” 默認的成功行為。 -
當認證不通過時,會執行它( 繼承自父類 )的 unsuccessfulAuthentication 方法。unsuccessfulAuthentication 的默認行為是再次顯示登陸頁面,並在頁面上提示用戶名密碼錯誤。
另外,你可以通過
http.failureHandler(...)
來 “覆蓋” 默認的失敗行為。
前面章節有講過,這里不再贅述了。
#2. 鑒權環節的異常處理
Spring Security 的認證工作是由 FilterSecurityInterceptor 處理的。FilterSecurityInterceptor 會拋出 2 種異常:
-
在用戶 “該登錄而未登錄” 時,拋出 AuthenticationException 異常;
默認情況下,拋出 AuthenticationException 異常時,Spring Security 返回 401 錯誤:未授權(Unauthorized)。
-
在用戶 “權限不夠” 時,拋出 AccessDeniedException 異常。
默認情況下,拋出 AccessDeniedException 異常時,Spring Security 返回 403 錯誤:被拒絕(Forbidden)訪問
在 Spring Security 配置中可以通過 http.exceptionHandling()
配置方法用來自定義鑒權環節的異常處理。配置風格如下:
http.exceptionHandling() .authenticationEntryPoint(...) .accessDeniedHandler(...);
其中:
-
AuthenticationEntryPoint 該類用來統一處理 AuthenticationException 異常;
-
AccessDeniedHandler 該類用來統一處理 AccessDeniedException 異常。
public class SimpleAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { HashMap<String, String> map = new HashMap<>(2); map.put("uri", request.getRequestURI()); map.put("msg", "你是不是沒登錄?"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); 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(); } }