J'utilise Spring MVC @ControllerAdvice
et @ExceptionHandler
pour gérer toutes les exceptions d'une API REST. Cela fonctionne bien pour les exceptions levées par les contrôleurs Web mvc, mais cela ne fonctionne pas pour les exceptions levées par les filtres personnalisés de sécurité Spring, car ils s'exécutent avant que les méthodes du contrôleur ne soient appelées.
J'ai un filtre de sécurité Spring personnalisé qui effectue une authentification basée sur des jetons:
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);
}
}
}
Avec ce point d'entrée personnalisé:
@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());
}
}
Et avec cette classe pour gérer les exceptions globalement:
@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;
}
}
Ce que je dois faire est de renvoyer un corps JSON détaillé, même pour Spring Security AuthenticationException. Existe-t-il un moyen de faire en sorte que Spring Security AuthenticationEntryPoint et spring mvc @ExceptionHandler fonctionnent ensemble?
J'utilise Spring Security 3.1.4 et Spring mvc 3.2.4.
la source
(@)ExceptionHandler
ne fonctionnera que si la demande est gérée par leDispatcherServlet
. Cependant, cette exception se produit avant cela car elle est levée par unFilter
. Vous ne pourrez donc jamais gérer cette exception avec un fichier(@)ExceptionHandler
.EntryPoint
. Vous voudrez peut-être y construire l'objet et y injecter unMappingJackson2HttpMessageConverter
.Réponses:
Ok, j'ai essayé comme suggéré d'écrire le json moi-même à partir de AuthenticationEntryPoint et cela fonctionne.
Juste pour tester, j'ai modifié AutenticationEntryPoint en supprimant response.sendError
De cette façon, vous pouvez envoyer des données json personnalisées avec le 401 non autorisé même si vous utilisez Spring Security AuthenticationEntryPoint.
Évidemment, vous ne construiriez pas le json comme je l'ai fait à des fins de test, mais vous sérialiseriez une instance de classe.
la source
C'est un problème très intéressant que Spring Security et Spring Web Framework ne sont pas tout à fait cohérents dans la manière dont ils gèrent la réponse. Je pense qu'il doit prendre en charge nativement la gestion des messages d'erreur de
MessageConverter
manière pratique.J'ai essayé de trouver un moyen élégant d'injecter
MessageConverter
dans Spring Security afin qu'ils puissent attraper l'exception et les renvoyer dans un bon format en fonction de la négociation du contenu . Pourtant, ma solution ci-dessous n'est pas élégante mais utilise au moins le code Spring.Je suppose que vous savez comment inclure la bibliothèque Jackson et JAXB, sinon il ne sert à rien de continuer. Il y a 3 étapes au total.
Étape 1 - Créez une classe autonome, en stockant MessageConverters
Cette classe ne joue pas de magie. Il stocke simplement les convertisseurs de messages et un processeur
RequestResponseBodyMethodProcessor
. La magie est à l'intérieur de ce processeur qui fera tout le travail, y compris la négociation de contenu et la conversion du corps de la réponse en conséquence.Étape 2 - Créer AuthenticationEntryPoint
Comme dans de nombreux didacticiels, cette classe est essentielle pour implémenter la gestion des erreurs personnalisée.
Étape 3 - Enregistrer le point d'entrée
Comme mentionné, je le fais avec Java Config. Je montre juste la configuration appropriée ici, il devrait y avoir une autre configuration telle que session sans état , etc.
Essayez avec certains cas d'échec d'authentification, rappelez-vous que l'en-tête de la demande doit inclure Accept: XXX et que vous devez obtenir l'exception au format JSON, XML ou dans d'autres formats.
la source
InvalidGrantException
mais ma version de votreCustomEntryPoint
n'est pas invoquée. Une idée de ce que je pourrais manquer?AuthenticationEntryPoint
etAccessDeniedHandler
telles queUsernameNotFoundException
etInvalidGrantException
peuvent être traitées parAuthenticationFailureHandler
comme expliqué ici .Le meilleur moyen que j'ai trouvé est de déléguer l'exception à HandlerExceptionResolver
alors vous pouvez utiliser @ExceptionHandler pour formater la réponse comme vous le souhaitez.
la source
@ControllerAdvice
ne fonctionnera pas si vous avez spécifié basePackages sur l'annotation. J'ai dû supprimer cela entièrement pour permettre au gestionnaire d'être appelé.@Component("restAuthenticationEntryPoint")
? Pourquoi le besoin d'un nom comme restAuthenticationEntryPoint? Est-ce pour éviter certaines collisions de noms Spring?Dans le cas de Spring Boot et
@EnableResourceServer
, il est relativement facile et pratique d'étendreResourceServerConfigurerAdapter
au lieu deWebSecurityConfigurerAdapter
dans la configuration Java et d'enregistrer une personnalisationAuthenticationEntryPoint
en remplaçantconfigure(ResourceServerSecurityConfigurer resources)
et en utilisantresources.authenticationEntryPoint(customAuthEntryPoint())
à l'intérieur de la méthode.Quelque chose comme ça:
Il y a aussi un joli
OAuth2AuthenticationEntryPoint
qui peut être étendu (puisqu'il n'est pas définitif) et partiellement réutilisé lors de l'implémentation d'un customAuthenticationEntryPoint
. En particulier, il ajoute des en-têtes "WWW-Authenticate" avec des détails relatifs aux erreurs.J'espère que cela aidera quelqu'un.
la source
commence()
fonction de monAuthenticationEntryPoint
n'est pas appelée - est-ce que je manque quelque chose?Prendre les réponses de @Nicola et @Victor Wing et ajouter une méthode plus standardisée:
Maintenant, vous pouvez injecter des Jackson, Jaxb configurés ou tout ce que vous utilisez pour convertir les corps de réponse sur votre annotation MVC ou votre configuration basée sur XML avec ses sérialiseurs, désérialiseurs, etc.
la source
<property name="messageConverter" ref="myConverterBeanName"/>
balise. Lorsque vous utilisez une@Configuration
classe, utilisez simplement lasetMessageConverter()
méthode.Nous devons utiliser
HandlerExceptionResolver
dans ce cas.En outre, vous devez ajouter la classe de gestionnaire d'exceptions pour renvoyer votre objet.
pouvez - vous obtenir une erreur au moment de l' exécution d' un projet en raison de multiples implémentations de
HandlerExceptionResolver
, Dans ce cas , vous devez ajouter@Qualifier("handlerExceptionResolver")
surHandlerExceptionResolver
la source
GenericResponseBean
est juste java pojo, pouvez-vous créer le vôtreJ'ai pu gérer cela en remplaçant simplement la méthode «unsuccessfulAuthentication» dans mon filtre. Là, j'envoie une réponse d'erreur au client avec le code d'état HTTP souhaité.
la source
Mise à jour: Si vous aimez et préférez voir le code directement, j'ai deux exemples pour vous, l'un utilisant Spring Security standard, ce que vous recherchez, l'autre utilisant l'équivalent de Reactive Web et Reactive Security:
- Normal Web + Jwt Security
- Reactive Jwt
Celui que j'utilise toujours pour mes points de terminaison basés sur JSON ressemble à ceci:
Le mappeur d'objet devient un bean une fois que vous ajoutez le démarreur Web de printemps, mais je préfère le personnaliser, voici donc mon implémentation pour ObjectMapper:
AuthenticationEntryPoint par défaut que vous définissez dans votre classe WebSecurityConfigurerAdapter:
la source
Personnalisez le filtre et déterminez quel type d'anomalie, il devrait y avoir une meilleure méthode que celle-ci
}
la source
J'utilise objectMapper. Chaque service de repos fonctionne principalement avec json, et dans l'une de vos configurations, vous avez déjà configuré un mappeur d'objets.
Le code est écrit en Kotlin, j'espère que ça ira.
la source
En
ResourceServerConfigurerAdapter
classe, le code ci-dessous a fonctionné pour moi.http.exceptionHandling().authenticationEntryPoint(new AuthFailureHandler()).and.csrf()..
n'a pas marché. C'est pourquoi je l'ai écrit séparément.Implémentation d'AuthenticationEntryPoint pour détecter l'expiration du jeton et l'en-tête d'autorisation manquant.
la source