Spring Security 異?;厥?/h1>
1. 前言
上一節中我們介紹了 Spring Security 中內置的安全過濾器,本節將介紹 Spring Security 的另一個基礎概念:「異常處理」。
這里所指異常是 Spring Security 在認證和鑒權過程中捕獲的異常,也就是訪問目標資源發生錯誤時的原因,Spring Security 以異常的形式處理「認證錯誤」和「權限錯誤」,并將結果傳回請求方。
學習 Spring Security 的異?;厥諜C制,有助于在開發和應用過程中排查問題。
2. 異常處理流程
Spring Security 的認證、授權異常在過濾器校驗過程中產生,并在 ExceptionTranslationFilter
中接收并進行處理,其流程如下:
ExceptionTranslationFilter
過濾器首先像其他過濾器一樣,調用過濾器鏈的執行方法FilterChain.doFilter(request, response)
啟動過濾處理;- 如果當前的用戶沒有通過認證或者因為其他原因在執行過程中拋出了
AuthenticationException
異常,此時將開啟「認證流程」:- 清空
SecurityContextHolder
對象; - 并將原始請求信息「request」保存到
RequestCache
對象中; - 使用
AuthenticationEntryPoint
對象存儲的認證地址,向客戶端索要身份證明。例如,使用瀏覽器登錄的用戶,將瀏覽器地址重定向到/login
或者回傳一個WWW-Authenticate
認證請求頭。
- 清空
- 如果當前用戶身份信息已確認,但是沒有訪問權限,則會產生
AccessDeniedException
異常,然后訪問被拒絕。繼續執行拒絕處理AccessDeniedHandler
。
假如認證過程中沒有產生「認證異?!够蛘摺笝嘞蕻惓!?,ExceptionTranslationFilter
則不做任何處理。
3. 異常的種類
3.1 認證異常
認證異常是在認證階段拋出的異常,其主要的實現類包括:
-
AccountStatusException
出現在賬戶狀態異常時候,比如認證憑據過期了、賬戶被鎖定了等。
-
ActiveDirectoryAuthenticationException
出現在 AD 域認證異常時。
-
AuthenticationCancelledException
出現在 OpenID 認證時,認證狀態被取消。
-
AuthenticationCredentialsNotFoundException
出現在無法找到認證憑證時,即
SecurityContext
實例中找不到Authentication
對象。 -
AuthenticationServiceException
出現在認證時遇到了后臺錯誤。
-
BadCredentialsException
出現在憑據檢查失敗,比如賬戶被禁用時。
-
InsufficientAuthenticationException
出現在以獲得憑據,但憑據不被信任的情況。
-
NonceExpiredException
出現在數字證書異常時。
-
OAuth2AuthenticationException
出現在 OAuth2 認證異常時。
-
PreAuthenticatedCredentialsNotFoundException
出現在預認證憑據未找到時。
-
ProviderNotFoundException
出現在當前認證方式不被支持時。
-
RememberMeAuthenticationException
出現在記住我認證失敗時。
-
Saml2AuthenticationException
出現在 Saml2 認證失敗時。
-
SessionAuthenticationException
出現在會話異常時,比如當前用戶創建的會話已經超過系統容量。
-
UsernameNotFoundException
出現在找不到用戶時。
3.2 權限異常
權限異常是在訪問資源階段拋出的異常,其主要的實現類包括:
-
AuthorizationServiceException
當鑒權請求無法完成或者,比如找不到目標方法時拋出此異常。
-
org.springframework.security.web.server.csrf.CsrfException
當 「CsrfToken」異?;蛉笔r拋出此異常。
-
org.springframework.security.web.csrf.CsrfException
當 「CsrfToken」異?;蛉笔r拋出此異常。
4. 自定義異常處理
當我們需要自定義異常處理時,需要在 HttpSecurity
對象的 exceptionHandling() 方法獲取異常處理的配置入口 ExceptionHandlingConfigurer
,并使用該類提供的 AuthenticationEntryPoint
和 AccessDeniedHandler
參數來配置異常處理:
AuthenticationEntryPoint
該類用來統一處理AuthenticationException
異常;AccessDeniedHandler
該類用來統一處理AccessDeniedException
異常。
4.1 自定義 AuthenticationEntryPoint
假設我們想統一異常 json 響應。
public class JSONAuthenticationEntryPoint 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("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 實現 AccessDeniedHandler
同樣假設我們想統一異常 json 響應。
public class JSONAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 實現個性化業務邏輯 ...
// 配置返回值
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();
}
}
5. 小結
本節我們介紹了 Spring Security 中的異?;厥諜C制:
- Spring Security 的異常處理是通過「安全過濾器」方式實現的;
- Spring Security 的異常分為兩大類,分別是「身份異?!购汀笝嘞蕻惓!?;
- Spring Security 可以通過修改配置自定義異常處理。
至此,關于 Spring Security 基礎部分就告一段了,下節開始我們進入應用環節,討論 Spring Security 的第一個重要模塊「認證」。