We are creating a RESTful API with spring MVC + spring security + hibernate. The API can produce both JSON and HTML. Doing a good error handling for spring security is giving me a headache:
Authentication can happen in various ways: BasicAuth, via different parameters in a POST request and also via web log-in.
For each authentication mechanism, there is a filter declared in the <http>
namespace element of the spring security xml config.
We handle all our spring exceptions in a custom HandlerExceptionResolver
. This works fine for all exceptions thrown in our controllers, but I don't know how to handle custom Exceptions thrown in the custom spring security filters.
Since the spring security filter comes before any of our controllers are invoked we do not see exceptions that we throw in our custom spring security filters.
I found this question here on stackoverflow:
Use custom exceptions in Spring Security. However I don't understand where they handle the exceptions that are thrown there.
We tried this approach but our custom HandlerExceptionResolver
is not called. Instead the user is presented with an ugly stacktrace rendered by tomcat.
Why do we need this?
Users can be activated and deactivated. If they are deactivated and try to perform certain actions we would like to return JSON with a custom error message. This should be different than what is displayed when spring security throws a AccessDeniedException
. The AccessDeniedException
somehow makes it to our HandlerExceptionResolver
, but I could not follow how exactly.
Possible solution
We thought about using an ExceptionTranslationFilter
, however this is not called when we throw our custom exceptions (set a breakpoint in the catch statement of the doFilter() method). In my understanding this catch block should be called and an authentication entry point should be used.
Another possibility: We could do something similar to the ExceptionTranslationFilter
in the spring security filter chain and do something similar to what its AccessDeniedHandler
does:
RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);
We could add some parameters (error code, reason etc.) to the request and have it point to a controller which would take care of the rendering in JSON or HTML.
Here is a short excerpt of our configuration:
Spring Security:
<http create-session="stateless" use-expressions="true" >
<!-- Try getting the authorization object from the request parameters. -->
<security:custom-filter ref="filter1" after="SECURITY_CONTEXT_FILTER"/>
<security:custom-filter ref="filter2" before="LOGOUT_FILTER"/>
<!-- Intercept certain URLS differently -->
<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />
<!-- Some more stuff here -->
<intercept-url pattern="/**" access="denyAll" />
<http-basic />
</http>
AppConfig of the HandlerExceptionResolver
@Bean
public HandlerExceptionResolver handlerExceptionResolver(){
logger.info("creating handler exception resolver");
return new AllExceptionHandler();
}
Our custom HandlerExceptionResolver
public class AllExceptionHandler implements HandlerExceptionResolver {
private static final Logger logger = LoggerFactory
.getLogger(AppConfig.class);
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
// This is just a snipped of the real method code
return new ModelAndView("errorPage");
}
The relevant part of one of our filters:
try {
Authentication authResult = authenticationManger.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
catch(AuthenticationException failed) {
SecurityContextHolder.clearContext();
throw failed;
}
Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>xxx.xxx.xxx.config</param-value>
</context-param>
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>LIVE</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<!-- Add multipart support for files up to 10 MB -->
<multipart-config>
<max-file-size>10000000</max-file-size>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>openEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<!-- Map filters -->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<error-page>
<error-code>404</error-code>
<location>/handle/404</location>
</error-page>
</web-app>
Does anyone have any pointers on how we could solve this? I looked through many articles on google, most of them describe how to handle the AccessDeniedException thrown by spring security when no filter is able to authenticate the request.
We're using Spring Security 3.1.0 and spring web mvc 3.1.0.
The @ExceptionHandler is an annotation used to handle the specific exceptions and sending the custom responses to the client. Define a class that extends the RuntimeException class. You can define the @ExceptionHandler method to handle the exceptions as shown.
Spring MVC Framework provides following ways to help us achieving robust exception handling. Controller Based - We can define exception handler methods in our controller classes. All we need is to annotate these methods with @ExceptionHandler annotation. This annotation takes Exception class as argument.
Spring MVC provides exception handling for your web application to make sure you are sending your own exception page instead of the server-generated exception to the user. The @ExceptionHandler annotation is used to detect certain runtime exceptions and send responses according to the exception.
Exception Handling in Spring Boot helps to deal with errors and exceptions present in APIs so as to deliver a robust enterprise application. This article covers various ways in which exceptions can be handled in a Spring Boot Project. Let's do the initial setup to explore each approach in more depth.
It's important to remember that the order of the filters in Spring Security matters.
From Spring Security 3 book:
The
ExceptionTranslationFilter
will be able to handle and react to only those exceptions that are thrown below it in the filter chain execution stack. Users often get confused, especially when adding custom filters in the incorrect order, as to why the expected behavior differs from their application's actual exception handling—in many of these cases, the order of the filters is to blame!
If your filters are about authorization it is a good practice to put them a the end of the chain as this approach is used by default authorization filters. That way you don't have to reinvent the wheel.
Standard filters: Table in documentation
After you properly configured your filter chain, you can configure error page, or even custom handler. More information available in documentation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With