172 votes

Comment obtenir les UserDetails d'un utilisateur actif ?

Dans mes contrôleurs, lorsque j'ai besoin de l'utilisateur actif (connecté), je procède de la manière suivante pour obtenir mes données UserDetails mise en œuvre :

User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.debug(activeUser.getSomeCustomField());

Cela fonctionne bien, mais j'aurais pensé que Spring pourrait faciliter la vie dans un cas comme celui-ci. Existe-t-il un moyen de faire en sorte que le UserDetails autoprogrammé dans le contrôleur ou la méthode ?

Par exemple, quelque chose comme :

public ModelAndView someRequestHandler(Principal principal) { ... }

Mais au lieu d'obtenir le UsernamePasswordAuthenticationToken Je reçois un UserDetails à la place ?

Je suis à la recherche d'une solution élégante. Des idées ?

232voto

Ralph Points 42744

Préambule : Depuis Spring-Security 3.2, il y a une annotation sympathique @AuthenticationPrincipal décrite à la fin de cette réponse. C'est la meilleure façon de procéder lorsque vous utilisez Spring-Security >= 3.2.

Quand vous :

  • utiliser une ancienne version de Spring-Security,
  • vous devez charger votre objet utilisateur personnalisé à partir de la base de données en fonction de certaines informations (comme le nom d'utilisateur ou l'identifiant) stockées dans le principal ou dans la base de données.
  • voulez apprendre comment un HandlerMethodArgumentResolver o WebArgumentResolver peut résoudre ce problème de manière élégante, ou si vous voulez simplement apprendre le contexte de l'opération. @AuthenticationPrincipal y AuthenticationPrincipalArgumentResolver (parce qu'il est basé sur un HandlerMethodArgumentResolver )

alors continuez à lire - sinon, utilisez simplement @AuthenticationPrincipal et merci à Rob Winch (Auteur de @AuthenticationPrincipal ) y Lukas Schmelzeisen (pour sa réponse).

(BTW : Ma réponse est un peu plus ancienne (janvier 2012), donc elle était Lukas Schmelzeisen qui apparaissent comme le premier avec le @AuthenticationPrincipal solution d'annotation basée sur Spring Security 3.2.)


Ensuite, vous pouvez utiliser dans votre contrôleur

public ModelAndView someRequestHandler(Principal principal) {
   User activeUser = (User) ((Authentication) principal).getPrincipal();
   ...
}

Ce n'est pas grave si vous en avez besoin une fois. Mais si vous en avez besoin plusieurs fois, c'est laid car cela pollue votre contrôleur avec des détails d'infrastructure, qui devraient normalement être cachés par le framework.

Ce que vous voulez vraiment, c'est un contrôleur comme celui-ci :

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
   ...
}

Par conséquent, il suffit d'implémenter un WebArgumentResolver . Il dispose d'une méthode

Object resolveArgument(MethodParameter methodParameter,
                   NativeWebRequest webRequest)
                   throws Exception

qui récupère la requête web (deuxième paramètre) et doit renvoyer la valeur User si elle se sent responsable de l'argument de la méthode (le premier paramètre).

Depuis Spring 3.1, il existe un nouveau concept appelé HandlerMethodArgumentResolver . Si vous utilisez Spring 3.1+, vous devriez l'utiliser. (Il est décrit dans la section suivante de cette réponse)).

public class CurrentUserWebArgumentResolver implements WebArgumentResolver{

   Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
        if(methodParameter is for type User && methodParameter is annotated with @ActiveUser) {
           Principal principal = webRequest.getUserPrincipal();
           return (User) ((Authentication) principal).getPrincipal();
        } else {
           return WebArgumentResolver.UNRESOLVED;
        }
   }
}

Vous devez définir l'annotation personnalisée -- Vous pouvez l'ignorer si chaque instance de User doit toujours être prise dans le contexte de sécurité, mais n'est jamais un objet de commande.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActiveUser {}

Dans la configuration, il suffit d'ajouter ceci :

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
    id="applicationConversionService">
    <property name="customArgumentResolver">
        <bean class="CurrentUserWebArgumentResolver"/>
    </property>
</bean>

@See : Apprenez à personnaliser les arguments de la méthode @Controller de Spring MVC.

Il est à noter que si vous utilisez Spring 3.1, ils recommandent HandlerMethodArgumentResolver plutôt que WebArgumentResolver. - voir le commentaire de Jay


La même chose avec HandlerMethodArgumentResolver pour Spring 3.1+.

public class CurrentUserHandlerMethodArgumentResolver
                               implements HandlerMethodArgumentResolver {

     @Override
     public boolean supportsParameter(MethodParameter methodParameter) {
          return
              methodParameter.getParameterAnnotation(ActiveUser.class) != null
              && methodParameter.getParameterType().equals(User.class);
     }

     @Override
     public Object resolveArgument(MethodParameter methodParameter,
                         ModelAndViewContainer mavContainer,
                         NativeWebRequest webRequest,
                         WebDataBinderFactory binderFactory) throws Exception {

          if (this.supportsParameter(methodParameter)) {
              Principal principal = webRequest.getUserPrincipal();
              return (User) ((Authentication) principal).getPrincipal();
          } else {
              return WebArgumentResolver.UNRESOLVED;
          }
     }
}

Dans la configuration, vous devez ajouter ceci

<mvc:annotation-driven>
      <mvc:argument-resolvers>
           <bean class="CurrentUserHandlerMethodArgumentResolver"/>         
      </mvc:argument-resolvers>
 </mvc:annotation-driven>

@See Exploitation de l'interface HandlerMethodArgumentResolver de Spring MVC 3.1


Solution Spring-Security 3.2

Spring Security 3.2 (ne pas confondre avec Spring 3.2) a sa propre solution intégrée : @AuthenticationPrincipal ( org.springframework.security.web.bind.annotation.AuthenticationPrincipal ) . Ceci est bien décrit dans Réponse de Lukas Schmelzeisen

C'est juste écrire

ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
 }

Pour que cela fonctionne, vous devez enregistrer le AuthenticationPrincipalArgumentResolver ( org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver ) : soit en "activant" le @EnableWebMvcSecurity ou en enregistrant ce haricot dans mvc:argument-resolvers - de la même façon que je l'ai décrit avec la solution Spring 3.1 ci-dessus.

@See Référence Spring Security 3.2, chapitre 11.2. @AuthenticationPrincipal


Solution Spring-Security 4.0

Cela fonctionne comme la solution de Spring 3.2, mais dans Spring 4.0, la fonction @AuthenticationPrincipal y AuthenticationPrincipalArgumentResolver a été "déplacé" vers un autre paquet :

(Mais les anciennes classes dans leurs anciens packs existent toujours, alors ne les mélangez pas !)

C'est juste écrire

import org.springframework.security.core.annotation.AuthenticationPrincipal;
ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
}

Pour que cela fonctionne, vous devez enregistrer l'option ( org.springframework.security.web.method.annotation. ) AuthenticationPrincipalArgumentResolver : soit en "activant" @EnableWebMvcSecurity ou en enregistrant ce haricot dans mvc:argument-resolvers - de la même façon que je l'ai décrit avec la solution Spring 3.1 ci-dessus.

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

@See Référence Spring Security 5.0, Chapitre 39.3 @AuthenticationPrincipal

0 votes

Vous pouvez aussi créer un bean avec une méthode getUserDetails() et l'intégrer dans votre contrôleur.

0 votes

En mettant en œuvre cette solution, j'ai placé un point d'arrêt au début de la fonction resolveArgument() mais mon application n'est jamais entrée dans le résolveur d'arguments Web. Votre configuration Spring dans le contexte de la servlet et non dans le contexte Root, n'est-ce pas ?

0 votes

@Jay : Cette configuration fait partie du contexte Root, pas du contexte servlet. -- Il semble que j'ai oublié de spécifier l'id. (id="applicationConversionService") dans l'exemple

67voto

Alors que Réponse de Ralphs fournit une solution élégante. Avec Spring Security 3.2, vous n'avez plus besoin d'implémenter votre propre système de gestion de la sécurité. ArgumentResolver .

Si vous avez un UserDetails mise en œuvre CustomUser tu peux juste faire ça :

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {

    // .. find messages for this User and return them...
}

Ver Documentation sur la sécurité de Spring : @AuthenticationPrincipal

2 votes

Pour ceux qui n'aiment pas lire les liens fournis, cela doit être activé soit par @EnableWebMvcSecurity ou en XML : <mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.Authent‌​icationPrincipalArgu‌​mentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>

0 votes

Comment vérifier si le customUser est nulle ou non ?

0 votes

if (customUser != null) { ... }

27voto

Luke Taylor Points 10160

Spring Security est destiné à fonctionner avec d'autres frameworks non-Spring, il n'est donc pas étroitement intégré à Spring MVC. Spring Security renvoie le Authentication de l'objet HttpServletRequest.getUserPrincipal() par défaut, c'est donc ce que vous obtenez comme principal. Vous pouvez obtenir votre UserDetails directement à partir de cet objet en utilisant

UserDetails ud = ((Authentication)principal).getPrincipal()

Notez également que les types d'objets peuvent varier en fonction du mécanisme d'authentification utilisé (il se peut que vous n'obteniez pas d'objet UsernamePasswordAuthenticationToken par exemple) et le Authentication ne doit pas nécessairement contenir un UserDetails . Il peut s'agir d'une chaîne ou de tout autre type.

Si vous ne voulez pas appeler SecurityContextHolder directement, l'approche la plus élégante (que je suivrais) consiste à injecter votre propre interface d'accès au contexte de sécurité personnalisée en fonction de vos besoins et des types d'objets utilisateur. Créez une interface, avec les méthodes pertinentes, par exemple :

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

Vous pouvez alors l'implémenter en accédant à l SecurityContextHolder dans votre implémentation standard, découplant ainsi entièrement votre code de Spring Security. Injectez-le ensuite dans les contrôleurs qui doivent accéder aux informations de sécurité ou aux informations sur l'utilisateur actuel.

L'autre avantage principal est qu'il est facile de faire des implémentations simples avec des données fixes pour les tests, sans avoir à se préoccuper de l'alimentation des thread-locals et ainsi de suite.

0 votes

J'ai envisagé cette approche, mais je n'étais pas sûr a) de la manière exacte de le faire de la bonne façon(tm) et b) des problèmes de filetage. Êtes-vous certain qu'il n'y aurait pas de problèmes ? Je vais utiliser la méthode d'annotation postée ci-dessus, mais je pense que c'est une bonne façon de procéder. Merci pour le message :)

4 votes

Techniquement, c'est la même chose que d'accéder au SecurityContextHolder directement depuis votre contrôleur, il ne devrait donc pas y avoir de problèmes de threading. Il s'agit simplement de garder l'appel à un seul endroit et de vous permettre d'injecter facilement des alternatives pour les tests. Vous pouvez également réutiliser la même approche dans d'autres classes non Web qui ont besoin d'accéder aux informations de sécurité.

0 votes

Compris... c'est ce que je pensais. Ce sera un bon point de départ si j'ai des problèmes de tests unitaires avec la méthode d'annotation, ou si je veux réduire le couplage avec Spring.

9voto

atrain Points 5653

Mettre en œuvre la HandlerInterceptor et ensuite injecter l'interface UserDetails dans chaque demande qui a un modèle, comme suit :

@Component 
public class UserInterceptor implements HandlerInterceptor {
    ....other methods not shown....
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if(modelAndView != null){
            modelAndView.addObject("user", (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        }
}

1 votes

Merci @atrain, c'est utile et élégant. De plus, j'ai dû ajouter le <mvc:interceptors> dans le fichier de configuration de mon application.

0 votes

Il est préférable d'obtenir l'utilisateur autorisé dans le modèle via spring-security-taglibs : stackoverflow.com/a/44373331/548473

9voto

geoand Points 10879

À partir de la version 3.2 de Spring Security, la fonctionnalité personnalisée qui a été mise en œuvre par certaines des réponses plus anciennes, existe d'emblée sous la forme de l'élément de configuration de la sécurité. @AuthenticationPrincipal qui est soutenue par AuthenticationPrincipalArgumentResolver .

Voici un exemple simple de son utilisation :

@Controller
public class MyController {
   @RequestMapping("/user/current/show")
   public String show(@AuthenticationPrincipal CustomUser customUser) {
        // do something with CustomUser
       return "view";
   }
}

Le CustomUser doit pouvoir être attribué à partir de authentication.getPrincipal()

Voici les Javadocs correspondants de AuthenticationPrincipal y AuthenticationPrincipalArgumentResolver

1 votes

@nbro Lorsque j'ai ajouté la solution spécifique à la version, aucune des autres solutions n'avait été mise à jour pour prendre en compte cette solution.

0 votes

AuthenticationPrincipalArgumentResolver est désormais obsolète.

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