How can I get my custom ResponseEntityExceptionHandler
or OAuth2ExceptionRenderer
to handle Exceptions raised by Spring security on a pure resource server?
We implemented a
@ControllerAdvice @RestController public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
so whenever there is an error on the resource server we want it to answer with
{ "message": "...", "type": "...", "status": 400 }
The resource server uses the application.properties setting:
security.oauth2.resource.userInfoUri: http://localhost:9999/auth/user
to authenticate and authorize a request against our auth server.
However any spring security error will always bypass our exception handler at
@ExceptionHandler(InvalidTokenException.class) public ResponseEntity<Map<String, Object>> handleInvalidTokenException(InvalidTokenException e) { return createErrorResponseAndLog(e, 401); }
and produce either
{ "timestamp": "2016-12-14T10:40:34.122Z", "status": 403, "error": "Forbidden", "message": "Access Denied", "path": "/api/templates/585004226f793042a094d3a9/schema" }
or
{ "error": "invalid_token", "error_description": "5d7e4ab5-4a88-4571-b4a4-042bce0a076b" }
So how do I configure the security exception handling for a resource server? All I ever find are examples on how to customize the Auth Server by implementing a custom OAuth2ExceptionRenderer
. But I can’t find where to wire this to the resource server’s security chain.
Our only configuration/setup is this:
@SpringBootApplication @Configuration @ComponentScan(basePackages = {"our.packages"}) @EnableAutoConfiguration @EnableResourceServer
Advertisement
Answer
As noted in previous comments the request is rejected by the security framework before it reaches the MVC layer so @ControllerAdvice
is not an option here.
There are 3 interfaces in the Spring Security framework that may be of interest here:
- org.springframework.security.web.authentication.AuthenticationSuccessHandler
- org.springframework.security.web.authentication.AuthenticationFailureHandler
- org.springframework.security.web.access.AccessDeniedHandler
You can create implementations of each of these Interfaces in order to customize the response sent for various events: successful login, failed login, attempt to access protected resource with insufficient permissions.
The following would return a JSON response on unsuccessful login attempt:
@Component public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException, ServletException { response.setStatus(HttpStatus.FORBIDDEN.value()); Map<String, Object> data = new HashMap<>(); data.put("timestamp", new Date()); data.put("status",HttpStatus.FORBIDDEN.value()); data.put("message", "Access Denied"); data.put("path", request.getRequestURL().toString()); OutputStream out = response.getOutputStream(); com.fasterxml.jackson.databind.ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(out, data); out.flush(); } }
You also need to register your implementation(s) with the Security framework. In Java config this looks like the below:
@Configuration @EnableWebSecurity @ComponentScan("...") public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http .addFilterBefore(corsFilter(), ChannelProcessingFilter.class) .logout() .deleteCookies("JESSIONID") .logoutUrl("/api/logout") .logoutSuccessHandler(logoutSuccessHandler()) .and() .formLogin() .loginPage("/login") .loginProcessingUrl("/api/login") .failureHandler(authenticationFailureHandler()) .successHandler(authenticationSuccessHandler()) .and() .csrf() .disable() .exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint()) .accessDeniedHandler(accessDeniedHandler()); } /** * @return Custom {@link AuthenticationFailureHandler} to send suitable response to REST clients in the event of a * failed authentication attempt. */ @Bean public AuthenticationFailureHandler authenticationFailureHandler() { return new RestAuthenticationFailureHandler(); } /** * @return Custom {@link AuthenticationSuccessHandler} to send suitable response to REST clients in the event of a * successful authentication attempt. */ @Bean public AuthenticationSuccessHandler authenticationSuccessHandler() { return new RestAuthenticationSuccessHandler(); } /** * @return Custom {@link AccessDeniedHandler} to send suitable response to REST clients in the event of an attempt to * access resources to which the user has insufficient privileges. */ @Bean public AccessDeniedHandler accessDeniedHandler() { return new RestAccessDeniedHandler(); } }