4 min read

Spring Security: Exception Handling

Spring Security: Exception Handling

In the previous post, I explained how authorization mechanism works in Spring Security according to successful scenario. In this article, I will explain what happens in case of authorization rejection, how we handle this scenario and return response back to user.

Authentication and authorization are very important steps when you implement secure application. However, client can make unauthenticated or not authorized request to a resource. In this case, you should force your client to provide credentials in order to access the resource. This can be done by redirecting your client to login page in user interface or informing about the situation in API response.

First, Let's see access denied scenario.

Spring Security: Access Denied flow
  1. Incoming request hits ExceptionTranslationFilter class.
  2. FilterSecurityInterceptor is the last filter class in FilterChain, which is responsible for authorization check. However, It is wrapped by ExceptionTranslationFilter in order to catch access denied exceptions.
  3. FilterSecurityInterceptor passes credentials and security metadata for decision.
  4. AccessDecisionManager asks for decision.
  5. AccessDecisionVoter rejects incoming request to access given resource.
  6. AccessDeniedException is thrown, so ExceptionTranslationFilter can catch it.

In case of rejection, you want to let client know why this happened. Spring Security provides built in solution for this case. Here you can see how ExceptionTranslationFilter behaves in such scenario.

Access Denied Handling

1. ExceptionTranslationFilter catches AccessDeniedException.

Then, flow will continue based on these if clauses.
a) If exception is thrown because client is not authenticated, then AuthenticationEntryPoint will be called.
b) If exception is thrown because client is not authorized for given resource, then AccessDeniedHandler will be called.

Okay, let's go one by one.

What is AuthenticationEntryPoint?

AuthenticationEntryPoint is an interface in Spring Security. According to official documentation, AuthenticationEntryPoint is used to send an HTTP response that requests credentials from a client.

In simple words, client needs to authenticate to access given resource.

Let's see available implementations for this interface in Spring Security.

Press Shift twice to open the Search Everywhere window in Intellij IDEA and type AuthenticationEntryPoint, then press Enter to open it. Next, press green I button next to the interface definition, as you see in the image below. This will show you list of default implementations for this interface.

Okay, next one...

What is AccessDecisionHandler?

AccessDecisionHandler is called when client is authenticated but not authorized to access given resource. Here, I add a screenshot of source code for this class. As you can see, there is not much happening except returning 403 HTTP code back to client.

Screenshot of AccessDeniedHandlerImpl source code

Okay, so you understand how Spring Security behaves in case of rejection. However, you might need custom implementations for both cases.

Let's check how it is done.

Custom AuthenticationEntryPoint and AccessDeniedHandler

In the code below, you can see how you can create custom implementations. I added header to let client know why their request is rejected, so client know what to do next.

@Component
class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, 
                       AccessDeniedException exc) throws IOException {
        response.addHeader("access_denied_reason", "not_authorized");
        response.sendError(403, "Access Denied");
    }
}

@Component
class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, 
                         AuthenticationException authException) throws IOException {
        response.addHeader("access_denied_reason", "authentication_required");
        response.sendError(403, "Access Denied");
    }
}

and finally, you need to let Spring Security know these implementations. You add .accessDeniedHandler() and .authenticationEntryPoint() after .exceptionHandling() configuration method.

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/user/**").hasRole("USER")
            // only ROLE_ADMIN can access to /admin/** endpoint pattern
            .and().authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
            // defining exception handling
            .and().exceptionHandling()
            // setting custom access denied handler for not authorized request
            .accessDeniedHandler(new CustomAccessDeniedHandler())
            // setting custom entry point for unauthenticated request
            .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
            // setting stateless session, because we choose to implement Rest API
            .and().sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

Conclusion

In these days, you mostly develop REST APIs and securing these applications are crucial. In this article, I showed how you can develop against access denied scenario.

If you would like to see a sample project for exception handling flow, please visit this repo. The code samples I shared in this article are available in this repo as well.

backendstory/spring-security-exception-handling at main · ugurcanlacin/backendstory
Contribute to ugurcanlacin/backendstory development by creating an account on GitHub.