How to manage exceptions thrown in filters in Spring?
So this is what I did:
I read the basics about filters here and I figured out that I need to create a custom filter that will be first in the filter chain and will have a try catch to catch all runtime exceptions that might occur there. Then i need to create the json manually and put it in the response.
So here is my custom filter:
public class ExceptionHandlerFilter extends OncePerRequestFilter {
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (RuntimeException e) {
// custom error response class used across my project
ErrorResponse errorResponse = new ErrorResponse(e);
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.getWriter().write(convertObjectToJson(errorResponse));
}
}
public String convertObjectToJson(Object object) throws JsonProcessingException {
if (object == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(object);
}
}
And then i added it in the web.xml before the CorsFilter
. And it works!
<filter>
<filter-name>exceptionHandlerFilter</filter-name>
<filter-class>xx.xxxxxx.xxxxx.api.controllers.filters.ExceptionHandlerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>exceptionHandlerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
exception handling for filter in spring
Filters happens before controllers are even resolved so exceptions thrown from filters can't be caught by a Controller Advice.
Filters are a part of the servlet and not really the MVC stack.
How to handle custom exceptions thrown by a filter in Spring Security
Spring security has a filter which is called the ExceptionTranslationFilter
which translates AccessDeniedException
and AuthenticationException
into responses. This filter catches these thrown exceptions in the spring security filter chain.
So if you want to return a custom exception, you could instead inherit from one of these classes instead of RuntimeException
and add a custom message.
I just want to emphasis and it can never be said too many times:
Providing friendly error messages in production applications when it comes to authentication/authorization is in general bad practice from a security standpoint. These types of messages can benefit malicious actors, when trying out things so that they realize what they have done wrong and guide them in their hacking attempts.
Providing friendly messages in test environments may be okey, but make sure that they are disabled in production. In production all failed authentication attempts a recommendation is to return a 401 with no additional information. And in graphical clients, generalized error messages should be displayed for instance "failed to authenticate" with no given specifics.
Also:
Writing custom security as you have done is also in general bad practice. Spring security is battle tested with 100000 of applications running it in production environments. Writing a custom filter to handle token and passwords, is in general not needed. Spring security already has implemented filters to handle security and authentication using standards like BASIC
authentication and TOKEN/JWT
. If you implement a non standard login, one bug might expose your application to a huge risk.
- Username and password authentication in spring
- Oauth2 authentication in spring
How to globally handle errors thrown from WebFilter in Spring WebFlux?
Three steps are required to get full control over all exceptions thrown from application endpoints handling code:
- Implement
org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler
- Annotate with
@ControllerAdvice
(or just@Component
) - Set
@Priority
less than1
to let the custom handler run before the default one (WebFluxResponseStatusExceptionHandler
)
The tricky part is where we get an instance implementing
ServerResponse.Context
for passing toServerResponse.writeTo(exchange, context)
. I did not find the final
answer, and comments are welcome. In the internal Spring code they always create a new instance of context for eachwriteTo
invocation,
although in all cases (I've manged to find) the context instance is immutable.
That is why I ended up using the sameResponseContextInstance
for all responses.
At the moment no problems detected with this approach.
@ControllerAdvice
@Priority(0) /* should go before WebFluxResponseStatusExceptionHandler */
class CustomWebExceptionHandler : ErrorWebExceptionHandler {
private val log = logger(CustomWebExceptionHandler::class)
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
log.error("handled ${ex.javaClass.simpleName}", ex)
val sr = when (ex) {
is FirstException -> handleFirst(ex)
is SecondException -> handleSecond(ex)
else -> defaultException(ex)
}
return sr.flatMap { it.writeTo(exchange, ResponseContextInstance) }.then()
}
private fun handleFirst(ex: FirstException): Mono<ServerResponse> {
return ServerResponse
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.bodyValue("first")
}
private fun handleSecond(ex: SecondException): Mono<ServerResponse> {
return ServerResponse.status(HttpStatus.BAD_REQUEST).bodyValue("second")
}
private object ResponseContextInstance : ServerResponse.Context {
val strategies: HandlerStrategies = HandlerStrategies.withDefaults()
override fun messageWriters(): List<HttpMessageWriter<*>> {
return strategies.messageWriters()
}
override fun viewResolvers(): List<ViewResolver> {
return strategies.viewResolvers()
}
}
}
Related Topics
Maven Compilation Error: (Use -Source 7 or Higher to Enable Diamond Operator)
Replace the Last Part of a String
Error: Servlet Jar Not Loaded... Offending Class: Javax/Servlet/Servlet.Class
Java 8 Instant.Now() with Nanosecond Resolution
Java Division by Zero Doesnt Throw an Arithmeticexception - Why
Why Equal Operator Works for Integer Value Until 128 Number
How to Ensure Hashcode() Is Consistent with Equals()
Comparing Float/Double Values Using == Operator
How to Create a Standalone .Exe in Java (That Runs Without an Installer and a Jre)
What Does an Assignment Expression Evaluate to in Java
How to Change the Dock Icon of a Java Program
Prevent SQL Injection Attacks in a Java Program
Running Multiple Launch Configurations at Once
Create a New Line in Java's Filewriter