Today, let’s talk about exception handling in Spring Security.

In Spring Security in the filter chain, ExceptionTranslationFilter filter designed to handle exceptions, and in the ExceptionTranslationFilter, we can see that anomaly is divided into two categories: Authentication exceptions and authorization exceptions are handled by different callback functions. Today Songo will share the rules here.

1. Anomaly classification

Exceptions in Spring Security can be divided into two broad categories, authentication exceptions and authorization exceptions.

The AuthenticationException is AuthenticationException, which has a number of implementation classes:

As you can see, there are many exception implementation classes, which are all authentication-related exceptions, that is, login failure exceptions. Some of these exceptions are described by Songo in previous articles, such as the following code (excerpted from Spring Security). All JSON interactions) :

resp.setContentType("application/json; charset=utf-8");
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error(e.getMessage());
if (e instanceof LockedException) {
    respBean.setMsg("Account locked, please contact administrator!");
} else if (e instanceof CredentialsExpiredException) {
    respBean.setMsg("Password expired, please contact administrator!");
} else if (e instanceof AccountExpiredException) {
    respBean.setMsg("Account expired, please contact administrator!");
} else if (e instanceof DisabledException) {
    respBean.setMsg("Account disabled, please contact administrator!");
} else if (e instanceof BadCredentialsException) {
    respBean.setMsg("Wrong user name or password, please re-enter!");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
Copy the code

Another type of authorization exception is AccessDeniedException, which has fewer implementation analogies because there are fewer possible causes of authorization failure.

2.ExceptionTranslationFilter

ExceptionTranslationFilter is responsible for handling exceptions in Spring Security filters, by default, the filter is automatically loaded into the filter chain.

Some friends may not be clear how to load, I here and you say a little.

When we use Spring Security, if you need a custom implementation logic, are inherited from WebSecurityConfigurerAdapter extensions, WebSecurityConfigurerAdapter itself on the part of the initialization, we’ll look at it inside HttpSecurity initialization process:

protected final HttpSecurity getHttp(a) throws Exception {
	if(http ! =null) {
		returnhttp; } 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;
}
Copy the code

As you can see, at the end of the getHttp method, configure(HTTP) is called; , when we are using Spring Security, custom configuration class inherits from WebSecurityConfigurerAdapter and rewrite the configure (HttpSecurity HTTP) method is invoked here, in other words, When we configure HttpSecurity, it has already completed a wave of initialization.

In the process of the default HttpSecurity initialization, calls the exceptionHandling method, this method will ExceptionHandlingConfigurer configuration to come in, Eventually call ExceptionHandlingConfigurer# configure method will ExceptionTranslationFilter added to the Spring Security in the filter chain.

We’ll look at ExceptionHandlingConfigurer# configure method source code:

@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);
}
Copy the code

As you can see, here is constructed two objects passed into the ExceptionTranslationFilter:

  • AuthenticationEntryPoint This is used to handle authentication exceptions.
  • AccessDeniedHandler This is used to handle authorization exceptions.

While the specific processing logic ExceptionTranslationFilter, we take a look at:

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 newRuntimeException(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); }}Copy the code

ExceptionTranslationFilter source is more long, here I listed part of the core and everyone analysis:

  1. The core of the filter is, of course, the doFilter method, from which we look. Here the doFilter method of filter chain continues to carry out, down ExceptionTranslationFilter in Spring Security filter chain the reciprocal of the second, the last is a FilterSecurityInterceptor, FilterSecurityInterceptor specifically to tackle the problem of authorization in addressing the problem of authorization, you will find the user is not login, unauthorized, etc., and then an exception is thrown, the exception thrown, will eventually be ExceptionTranslationFilter# doFilter method to capture.
  2. When the exception is caught, the next step is through the callthrowableAnalyzer.getFirstThrowableOfTypeIs the method to judge the certification or authorized anomaly, judge the exception type, enter the handleSpringSecurityException method for processing; If it is not an exception type in Spring Security, follow the logic of the ServletException exception type.
  3. Into the handleSpringSecurityException method, or judgment, depending on the type of exception if is certification related to abnormal, go sendStartAuthentication method, Finally by authenticationEntryPoint.com mence treatments; If it is authorized related abnormalities, and then walk accessDeniedHandler. Handle method for processing.

The default implementation class is LoginUrlAuthenticationEntryPoint AuthenticationEntryPoint, So the default authentication exception handling logic is LoginUrlAuthenticationEntryPoint# commence method, as follows:

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);
}
Copy the code

As you can see, it is redirected to the login page (i.e., we are automatically redirected to the login page when accessing a resource that requires login).

The default implementation class of AccessDeniedHandler is AccessDeniedHandlerImpl, so authorization exceptions are handled by default in 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()); }}}Copy the code

As you can see, this is the server jump returning 403.

3. Customize processing

In the previous section, we introduced the default processing logic in Spring Security. In actual development, we can make some adjustments. It is very simple to configure exceptionHandling.

First, customize the authentication exception handling class and authorization exception handling class:

@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()); }}Copy the code

Then configure it in SecurityConfig as follows:

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

After configuration, restart the project, authentication and authorization exceptions will follow our custom logic.

4. Summary

The exception handling mechanism in Spring Security is the exception handling mechanism in Spring Security

Download address: github.com/lenve/sprin…

Spring Security, Spring Security series 40+ complete articles ~