欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文搞懂Spring?Security異常處理機(jī)制

 更新時間:2022年07月08日 10:12:19   作者:CoderXiong  
這篇文章主要為大家詳細(xì)介紹一下Spring?Security異常處理機(jī)制,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Spring?Security有一定幫助,感興趣的可以學(xué)習(xí)一下

今天來和小伙伴們聊一聊 Spring Security 中的異常處理機(jī)制。

在 Spring Security 的過濾器鏈中,ExceptionTranslationFilter 過濾器專門用來處理異常,在 ExceptionTranslationFilter 中,我們可以看到,異常被分為了兩大類:認(rèn)證異常和授權(quán)異常,兩種異常分別由不同的回調(diào)函數(shù)來處理,今天就來和大家分享一下這里的條條框框。

1.異常分類

Spring Security 中的異??梢苑譃閮纱箢?,一種是認(rèn)證異常,一種是授權(quán)異常。

認(rèn)證異常就是 AuthenticationException,它有眾多的實(shí)現(xiàn)類:

可以看到,這里的異常實(shí)現(xiàn)類還是蠻多的,都是都是認(rèn)證相關(guān)的異常,也就是登錄失敗的異常。這些異常,有的松哥在之前的文章中都和大家介紹過了,例如下面這段代碼

resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error(e.getMessage());
if (e instanceof LockedException) {
    respBean.setMsg("賬戶被鎖定,請聯(lián)系管理員!");
} else if (e instanceof CredentialsExpiredException) {
    respBean.setMsg("密碼過期,請聯(lián)系管理員!");
} else if (e instanceof AccountExpiredException) {
    respBean.setMsg("賬戶過期,請聯(lián)系管理員!");
} else if (e instanceof DisabledException) {
    respBean.setMsg("賬戶被禁用,請聯(lián)系管理員!");
} else if (e instanceof BadCredentialsException) {
    respBean.setMsg("用戶名或者密碼輸入錯誤,請重新輸入!");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();

另一類就是授權(quán)異常 AccessDeniedException,授權(quán)異常的實(shí)現(xiàn)類比較少,因?yàn)槭跈?quán)失敗的可能原因比較少。

2.ExceptionTranslationFilter

ExceptionTranslationFilter 是 Spring Security 中專門負(fù)責(zé)處理異常的過濾器,默認(rèn)情況下,這個過濾器已經(jīng)被自動加載到過濾器鏈中。

有的小伙伴可能不清楚是怎么被加載的,我這里和大家稍微說一下。

當(dāng)我們使用 Spring Security 的時候,如果需要自定義實(shí)現(xiàn)邏輯,都是繼承自 WebSecurityConfigurerAdapter 進(jìn)行擴(kuò)展,WebSecurityConfigurerAdapter 中本身就進(jìn)行了一部分的初始化操作,我們來看下它里邊 HttpSecurity 的初始化過程:

protected final HttpSecurity getHttp() throws Exception {
	if (http != null) {
		return http;
	}
	AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
	localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
	AuthenticationManager authenticationManager = authenticationManager();
	authenticationBuilder.parentAuthenticationManager(authenticationManager);
	Map<Class<?>, Object> sharedObjects = createSharedObjects();
	http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
			sharedObjects);
	if (!disableDefaults) {
		http
			.csrf().and()
			.addFilter(new WebAsyncManagerIntegrationFilter())
			.exceptionHandling().and()
			.headers().and()
			.sessionManagement().and()
			.securityContext().and()
			.requestCache().and()
			.anonymous().and()
			.servletApi().and()
			.apply(new DefaultLoginPageConfigurer<>()).and()
			.logout();
		ClassLoader classLoader = this.context.getClassLoader();
		List<AbstractHttpConfigurer> defaultHttpConfigurers =
				SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
		for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
			http.apply(configurer);
		}
	}
	configure(http);
	return http;
}

可以看到,在 getHttp 方法的最后,調(diào)用了 configure(http);,我們在使用 Spring Security 時,自定義配置類繼承自 WebSecurityConfigurerAdapter 并重寫的 configure(HttpSecurity http) 方法就是在這里調(diào)用的,換句話說,當(dāng)我們?nèi)ヅ渲?HttpSecurity 時,其實(shí)它已經(jīng)完成了一波初始化了。

在默認(rèn)的 HttpSecurity 初始化的過程中,調(diào)用了 exceptionHandling 方法,這個方法會將 ExceptionHandlingConfigurer 配置進(jìn)來,最終調(diào)用 ExceptionHandlingConfigurer#configure 方法將 ExceptionTranslationFilter 添加到 Spring Security 過濾器鏈中。

我們來看下 ExceptionHandlingConfigurer#configure 方法源碼:

@Override
public void configure(H http) {
	AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
	ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
			entryPoint, getRequestCache(http));
	AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
	exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
	exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
	http.addFilter(exceptionTranslationFilter);
}

可以看到,這里構(gòu)造了兩個對象傳入到 ExceptionTranslationFilter 中:

  • AuthenticationEntryPoint 這個用來處理認(rèn)證異常。
  • AccessDeniedHandler 這個用來處理授權(quán)異常。

具體的處理邏輯則在 ExceptionTranslationFilter 中,我們來看一下:

public class ExceptionTranslationFilter extends GenericFilterBean {
	public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint,
			RequestCache requestCache) {
		this.authenticationEntryPoint = authenticationEntryPoint;
		this.requestCache = requestCache;
	}
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		try {
			chain.doFilter(request, response);
		}
		catch (IOException ex) {
			throw ex;
		}
		catch (Exception ex) {
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
			RuntimeException ase = (AuthenticationException) throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);
			if (ase == null) {
				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
						AccessDeniedException.class, causeChain);
			}
			if (ase != null) {
				if (response.isCommitted()) {
					throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
				}
				handleSpringSecurityException(request, response, chain, ase);
			}
			else {
				if (ex instanceof ServletException) {
					throw (ServletException) ex;
				}
				else if (ex instanceof RuntimeException) {
					throw (RuntimeException) ex;
				}
				throw new RuntimeException(ex);
			}
		}
	}
	private void handleSpringSecurityException(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, RuntimeException exception)
			throws IOException, ServletException {
		if (exception instanceof AuthenticationException) {
			sendStartAuthentication(request, response, chain,
					(AuthenticationException) exception);
		}
		else if (exception instanceof AccessDeniedException) {
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
				sendStartAuthentication(
						request,
						response,
						chain,
						new InsufficientAuthenticationException(
							messages.getMessage(
								"ExceptionTranslationFilter.insufficientAuthentication",
								"Full authentication is required to access this resource")));
			}
			else {
				accessDeniedHandler.handle(request, response,
						(AccessDeniedException) exception);
			}
		}
	}
	protected void sendStartAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain,
			AuthenticationException reason) throws ServletException, IOException {
		SecurityContextHolder.getContext().setAuthentication(null);
		requestCache.saveRequest(request, response);
		logger.debug("Calling Authentication entry point.");
		authenticationEntryPoint.commence(request, response, reason);
	}
}

ExceptionTranslationFilter 的源碼比較長,我這里列出來核心的部分和大家分析:

  • 過濾器最核心的當(dāng)然是 doFilter 方法,我們就從 doFilter 方法看起。這里的 doFilter 方法中過濾器鏈繼續(xù)向下執(zhí)行,ExceptionTranslationFilter 處于 Spring Security 過濾器鏈的倒數(shù)第二個,最后一個是 FilterSecurityInterceptor,F(xiàn)ilterSecurityInterceptor 專門處理授權(quán)問題,在處理授權(quán)問題時,就會發(fā)現(xiàn)用戶未登錄、未授權(quán)等,進(jìn)而拋出異常,拋出的異常,最終會被 ExceptionTranslationFilter#doFilter 方法捕獲。
  • 當(dāng)捕獲到異常之后,接下來通過調(diào)用 throwableAnalyzer.getFirstThrowableOfType 方法來判斷是認(rèn)證異常還是授權(quán)異常,判斷出異常類型之后,進(jìn)入到 handleSpringSecurityException 方法進(jìn)行處理;如果不是 Spring Security 中的異常類型,則走 ServletException 異常類型的處理邏輯。
  • 進(jìn)入到 handleSpringSecurityException 方法之后,還是根據(jù)異常類型判斷,如果是認(rèn)證相關(guān)的異常,就走 sendStartAuthentication 方法,最終被 authenticationEntryPoint.commence 方法處理;如果是授權(quán)相關(guān)的異常,就走 accessDeniedHandler.handle 方法進(jìn)行處理。

AuthenticationEntryPoint 的默認(rèn)實(shí)現(xiàn)類是 LoginUrlAuthenticationEntryPoint,因此默認(rèn)的認(rèn)證異常處理邏輯就是 LoginUrlAuthenticationEntryPoint#commence 方法,如下:

public void commence(HttpServletRequest request, HttpServletResponse response,
		AuthenticationException authException) throws IOException, ServletException {
	String redirectUrl = null;
	if (useForward) {
		if (forceHttps && "http".equals(request.getScheme())) {
			redirectUrl = buildHttpsRedirectUrlForRequest(request);
		}
		if (redirectUrl == null) {
			String loginForm = determineUrlToUseForThisRequest(request, response,
					authException);
			RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
			dispatcher.forward(request, response);
			return;
		}
	}
	else {
		redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
	}
	redirectStrategy.sendRedirect(request, response, redirectUrl);
}

可以看到,就是重定向,重定向到登錄頁面(即當(dāng)我們未登錄就去訪問一個需要登錄才能訪問的資源時,會自動重定向到登錄頁面)。

AccessDeniedHandler 的默認(rèn)實(shí)現(xiàn)類則是 AccessDeniedHandlerImpl,所以授權(quán)異常默認(rèn)是在 AccessDeniedHandlerImpl#handle 方法中處理的:

public void handle(HttpServletRequest request, HttpServletResponse response,
		AccessDeniedException accessDeniedException) throws IOException,
		ServletException {
	if (!response.isCommitted()) {
		if (errorPage != null) {
			request.setAttribute(WebAttributes.ACCESS_DENIED_403,
					accessDeniedException);
			response.setStatus(HttpStatus.FORBIDDEN.value());
			RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
			dispatcher.forward(request, response);
		}
		else {
			response.sendError(HttpStatus.FORBIDDEN.value(),
				HttpStatus.FORBIDDEN.getReasonPhrase());
		}
	}
}

可以看到,這里就是服務(wù)端跳轉(zhuǎn)返回 403。

3.自定義處理

前面和大家介紹了 Spring Security 中默認(rèn)的處理邏輯,實(shí)際開發(fā)中,我們可以需要做一些調(diào)整,很簡單,在 exceptionHandling 上進(jìn)行配置即可。

首先自定義認(rèn)證異常處理類和授權(quán)異常處理類:

@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.getWriter().write("login failed:" + authException.getMessage());
    }
}
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setStatus(403);
        response.getWriter().write("Forbidden:" + accessDeniedException.getMessage());
    }
}

然后在 SecurityConfig 中進(jìn)行配置,如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                ...
                ...
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(myAuthenticationEntryPoint)
                .accessDeniedHandler(myAccessDeniedHandler)
                .and()
                ...
                ...
    }
}

配置完成后,重啟項(xiàng)目,認(rèn)證異常和授權(quán)異常就會走我們自定義的邏輯了。

到此這篇關(guān)于一文搞懂Spring Security異常處理機(jī)制的文章就介紹到這了,更多相關(guān)Spring Security異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論