今天學習的filter是ExceptionTranslationFilter,看名字是異常翻譯過濾器
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
try {
// 進入下一個filter
chain.doFilter(request, response);
this.logger.debug("Chain processed normally");
} catch (IOException var9) {
throw var9;
} catch (Exception var10) {
// 獲取Thrrowable
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
// 判斷異常是否是AuthenticationException
RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
// 判斷異常是否是AccessDeniedException
ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (ase == null) {
if (var10 instanceof ServletException) {
throw (ServletException)var10;
}
if (var10 instanceof RuntimeException) {
throw (RuntimeException)var10;
}
throw new RuntimeException(var10);
}
// 如果是AccessDeniedException 或者AuthenticationException則進入如下方法
this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
}
}
下面是handleSpringSecurityException方法的源碼
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);
} else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) {
this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
} else {
this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception);
this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException("Full authentication is required to access this resource"));
}
}
}
1.handleSpringSecurityException方法首先判斷是否是AuthenticationException異常,如果是先用RequestCache保存前一步鏈接(到時候登錄成功之後可以直接跳到用戶操作的url),然後跳轉到登錄頁面。
跳轉到登錄界面前,HttpSessionRequestCache會保存當前請求的url,登錄認證通過後會直接跳轉到之前訪問的頁面。使用這個功能不能將<form-login>的always-use-default-target屬性設置爲TRUE,默認是FALSE的。這個邏輯在SavedRequestAwareAuthenticationSuccessHandler中處理。
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
// SecurityContextHolderz中的Authentication設置爲null
SecurityContextHolder.getContext().setAuthentication((Authentication)null);
// 緩存用戶的前一步請求的url
this.requestCache.saveRequest(request, response);
this.logger.debug("Calling Authentication entry point.");
// 調用接口authenticationEntryPoint的commence方法處理異常的後續操作
this.authenticationEntryPoint.commence(request, response, reason);
}
2,如果是AccessDeniedException,先判斷是否是匿名用戶,如果是,也將跳轉到登錄頁面,如果是已認證用戶,則交給accessDeniedHandler處理。
if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) {
this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
} else {
// 如果是匿名用戶,也將跳轉到登錄頁面
this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception);
this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException("Full authentication is required to access this resource"));
}
針對上訴總結:
ExceptionTranslationFilter 是Spring Security的核心filter之一,用來處理AuthenticationException和AccessDeniedException兩種異常。
在我們的例子中,AuthenticationException指的是未登錄狀態下訪問受保護資源,AccessDeniedException指的是登陸了但是由於權限不足(比如普通用戶訪問管理員界面)。
ExceptionTranslationFilter 持有兩個處理類,分別是AuthenticationEntryPoint和AccessDeniedHandler。
ExceptionTranslationFilter 對異常的處理是通過這兩個處理類實現的,處理規則很簡單:
規則1. 如果異常是 AuthenticationException,使用 AuthenticationEntryPoint 處理
規則2. 如果異常是 AccessDeniedException 且用戶是匿名用戶,使用 AuthenticationEntryPoint 處理
規則3. 如果異常是 AccessDeniedException 且用戶不是匿名用戶,如果否則交給 AccessDeniedHandler 處理。
AccessDeniedHandler 默認實現是 AccessDeniedHandlerImpl。
AuthenticationEntryPoint 默認實現是 LoginUrlAuthenticationEntryPoint, 該類的處理是轉發或重定向到登錄頁面
如果想替換默認的,直接在寫這段話
http.exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint).accessDeniedHandler(myAccessDeniedHandler);
因爲exceptionHandling這個類提供了相應的set方法