Nous créons une API RESTful avec spring MVC + spring security + hibernate. L'API peut produire à la fois du JSON et du HTML. Faire une bonne gestion des erreurs pour spring security me donne des maux de tête :
L'authentification peut se faire de différentes manières : BasicAuth, via différents paramètres dans une requête POST et également via une connexion web. Pour chaque mécanisme d'authentification, un filtre est déclaré dans le fichier <http>
de l'élément namespace de la configuration de spring security xml.
Nous gérons toutes nos exceptions spring dans un HandlerExceptionResolver
. Cela fonctionne bien pour toutes les exceptions lancées dans nos contrôleurs, mais je ne sais pas comment gérer les exceptions personnalisées lancées dans les filtres de sécurité personnalisés de Spring. Étant donné que le filtre de sécurité Spring intervient avant l'invocation de nos contrôleurs, nous ne voyons pas les exceptions que nous lançons dans nos filtres de sécurité Spring personnalisés.
J'ai trouvé cette question sur stackoverflow : Utiliser des exceptions personnalisées dans Spring Security . Cependant, je ne comprends pas comment ils gèrent les exceptions qui sont lancées. Nous avons essayé cette approche mais notre HandlerExceptionResolver
n'est pas appelé. Au lieu de cela, l'utilisateur est confronté à un horrible tracé de pile rendu par Tomcat.
Pourquoi avons-nous besoin de cela ? Les utilisateurs peuvent être activés et désactivés. S'ils sont désactivés et qu'ils essaient d'effectuer certaines actions, nous aimerions renvoyer du JSON avec un message d'erreur personnalisé. Ce message doit être différent de celui qui est affiché lorsque Spring Security envoie un message d'erreur de type AccessDeniedException
. Les AccessDeniedException
d'une manière ou d'une autre à notre HandlerExceptionResolver
mais je n'ai pas pu suivre exactement comment.
Solution possible Nous avons envisagé d'utiliser un ExceptionTranslationFilter
Cependant, elle n'est pas appelée lorsque nous lançons nos exceptions personnalisées (en plaçant un point d'arrêt dans l'instruction catch de la méthode doFilter()). Selon moi, ce bloc de capture devrait être appelé et un point d'entrée d'authentification devrait être utilisé.
Autre possibilité : Nous pourrions faire quelque chose de similaire à la ExceptionTranslationFilter
dans la chaîne de filtrage de la sécurité du printemps et faire quelque chose de similaire à ce que son AccessDeniedHandler
ne :
RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);
Nous pourrions ajouter quelques paramètres (code d'erreur, raison, etc.) à la requête et la faire pointer vers un contrôleur qui se chargerait du rendu en JSON ou HTML.
Voici un bref extrait de notre configuration :
Sécurité de printemps :
<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 du résolveur d'exceptions HandlerExceptionResolver
@Bean
public HandlerExceptionResolver handlerExceptionResolver(){
logger.info("creating handler exception resolver");
return new AllExceptionHandler();
}
Notre HandlerExceptionResolver personnalisé
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");
}
La partie pertinente de l'un de nos filtres :
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>
Quelqu'un a-t-il des indications sur la manière de résoudre ce problème ? J'ai regardé de nombreux articles sur google, la plupart d'entre eux décrivent comment gérer l'exception AccessDeniedException lancée par Spring Security lorsqu'aucun filtre n'est capable d'authentifier la requête.
Nous utilisons Spring Security 3.1.0 et Spring Web MVC 3.1.0.