21 votes

Gérer les exceptions personnalisées dans Spring Security

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.

11voto

Maciej Ziarko Points 3236

Il est important de se rappeler que l'ordre des filtres dans Spring Security est important.

De Sécurité du printemps 3 livre :

En ExceptionTranslationFilter sera capable de gérer et de réagir à que les exceptions lancées en dessous d'elle dans la pile d'exécution de la chaîne de filtrage. la pile d'exécution. Les utilisateurs sont souvent désorientés, en particulier lorsqu'ils ajoutent des filtres personnalisés dans le mauvais ordre. filtres personnalisés dans un ordre incorrect, pour savoir pourquoi le comportement attendu diffère de la gestion réelle des exceptions de leur application. dans de nombreux cas, c'est l'ordre des filtres qui est en cause !

Si vos filtres concernent l'autorisation, il est préférable de les placer à la fin de la chaîne, car cette approche est utilisée par les filtres d'autorisation par défaut. Ainsi, vous n'aurez pas à réinventer la roue.

Filtres standard : Tableau dans la documentation

Après avoir correctement configuré votre chaîne de filtrage, vous pouvez configurer une page d'erreur, ou même un gestionnaire personnalisé. Plus d'informations sont disponibles dans la documentation .

1voto

user469718 Points 35

Je vois que ExceptionTranslationFilter ne gère que deux exceptions AuthenticationException et AccessDeniedException avec des gestionnaires personnalisés pour ces deux exceptions, mais qu'en est-il des autres types d'exceptions ou même des exceptions au moment de l'exécution ?

Comment gérer/intercepter à peu près n'importe quelle exception dans la pile de filtres de Spring ? N'existe-t-il pas un moyen standard pour Spring d'attraper et d'obtenir une requête (en dehors de l'écriture d'un filtre personnalisé au-dessus de tout), une réponse sans écrire un autre filtre au-dessus de tout ?

<security:http auto-config="false" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="authenticationEntryPoint"
pattern="/**">

<security:custom-filter before="FIRST" ref="stackExceptionFilter" />

<security:custom-filter before="..." ref="authenticationFilter" />
<security:logout />
</security:http> 

J'ai fini par ajouter un autre filtre juste au-dessus (ou configurer le filtre pour /* dans web.xml) qui avait simplement un bloc try catch et déléguait toute exception non capturée à un composant Spring de gestion d'exception personnalisé appelant une méthode ExceptionController (chaque méthode renvoyant un type de réponse différent de différentes manières) d'une manière sûre et renvoyant également des messages d'exception personnalisés basés sur le type d'exception (notre exigence). La seule difficulté a été d'ajouter un peu de logique afin d'éviter les boucles. Le Spring custom ExceptionHandlerExceptionResolver et @ExceptionHandler dans les contrôleurs ne gèrent pas les exceptions de filtre et ont une limitation sur la façon dont vous voulez retourner un message d'exception comme (XML/JSON, redirect, forward,....). Cela suppose que vous disposiez d'une bonne hiérarchie d'exceptions d'application qui capture les exceptions et les rejette avec des informations de référence pertinentes, car les filtres n'ont rien de tel.

Même chose pour les codes d'erreur, définir des pages statiques dans le fichier web.xml mais les capturer en associant un filtre au distributeur ERROR et en préparant un modèle pour les pages affichant le code d'erreur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X