I'm using Spring MVC's @ControllerAdvice
and @ExceptionHandler
to handle all the exception of a REST Api. It works fine for exceptions thrown by web mvc controllers but it does not work for exceptions thrown by spring security custom filters because they run before the controller methods are invoked.
I have a custom spring security filter that does a token based auth:
public class AegisAuthenticationFilter extends GenericFilterBean {
...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try {
...
} catch(AuthenticationException authenticationException) {
SecurityContextHolder.clearContext();
authenticationEntryPoint.commence(request, response, authenticationException);
}
}
}
With this custom entry point:
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}
And with this class to handle exceptions globally:
@ControllerAdvice
public class RestEntityResponseExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({ InvalidTokenException.class, AuthenticationException.class })
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
@ResponseBody
public RestError handleAuthenticationException(Exception ex) {
int errorCode = AegisErrorCode.GenericAuthenticationError;
if(ex instanceof AegisException) {
errorCode = ((AegisException)ex).getCode();
}
RestError re = new RestError(
HttpStatus.UNAUTHORIZED,
errorCode,
"...",
ex.getMessage());
return re;
}
}
What I need to do is to return a detailed JSON body even for spring security AuthenticationException. Is there a way make spring security AuthenticationEntryPoint and spring mvc @ExceptionHandler work together?
I'm using spring security 3.1.4 and spring mvc 3.2.4.
The best way I've found is to delegate the exception to the HandlerExceptionResolver
then you can use @ExceptionHandler to format the response the way you want.
I'm using the objectMapper. Every Rest Service is mostly working with json, and in one of your configs you have already configured an object mapper.
Code is written in Kotlin, hopefully it will be ok.
We need to use
HandlerExceptionResolver
in that case.Also, you need to add in the exception handler class to return your object.
may you get an error at the time of running a project because of multiple implementations of
HandlerExceptionResolver
, In that case you have to add@Qualifier("handlerExceptionResolver")
onHandlerExceptionResolver
This is a very interesting problem that Spring Security and Spring Web framework is not quite consistent in the way they handle the response. I believe it has to natively support error message handling with
MessageConverter
in a handy way.I tried to find an elegant way to inject
MessageConverter
into Spring Security so that they could catch the exception and return them in a right format according to content negotiation. Still, my solution below is not elegant but at least make use of Spring code.I assume you know how to include Jackson and JAXB library, otherwise there is no point to proceed. There are 3 Steps in total.
Step 1 - Create a standalone class, storing MessageConverters
This class plays no magic. It simply stores the message converters and a processor
RequestResponseBodyMethodProcessor
. The magic is inside that processor which will do all the job including content negotiation and converting the response body accordingly.Step 2 - Create AuthenticationEntryPoint
As in many tutorials, this class is essential to implement custom error handling.
Step 3 - Register the entry point
As mentioned, I do it with Java Config. I just show the relevant configuration here, there should be other configuration such as session stateless, etc.
Try with some authentication fail cases, remember the request header should include Accept : XXX and you should get the exception in JSON, XML or some other formats.
Ok, I tried as suggested writing the json myself from the AuthenticationEntryPoint and it works.
Just for testing I changed the AutenticationEntryPoint by removing response.sendError
In this way you can send custom json data along with the 401 unauthorized even if you are using Spring Security AuthenticationEntryPoint.
Obviously you would not build the json as I did for testing purposes but you would serialize some class instance.
In case of Spring Boot and
@EnableResourceServer
, it is relatively easy and convenient to extendResourceServerConfigurerAdapter
instead ofWebSecurityConfigurerAdapter
in the Java configuration and register a customAuthenticationEntryPoint
by overridingconfigure(ResourceServerSecurityConfigurer resources)
and usingresources.authenticationEntryPoint(customAuthEntryPoint())
inside the method.Something like this:
There's also a nice
OAuth2AuthenticationEntryPoint
that can be extended (since it's not final) and partially re-used while implementing a customAuthenticationEntryPoint
. In particular, it adds "WWW-Authenticate" headers with error-related details.Hope this will help someone.