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