Comment obtenir les UserDetails de l'utilisateur actif

170

Dans mes contrôleurs, lorsque j'ai besoin de l'utilisateur actif (connecté), je fais ce qui suit pour obtenir mon UserDetailsimplémentation:

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

Cela fonctionne bien, mais je pense que Spring pourrait rendre la vie plus facile dans un cas comme celui-ci. Existe-t-il un moyen de UserDetailsconnecter automatiquement le contrôleur au contrôleur ou à la méthode?

Par exemple, quelque chose comme:

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

Mais au lieu d'obtenir le UsernamePasswordAuthenticationToken, j'obtiens un UserDetails?

Je recherche une solution élégante. Des idées?

L'ours effrayé
la source

Réponses:

226

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

Lorsque vous:

  • utiliser une ancienne version de Spring-Security,
  • besoin de charger votre objet utilisateur personnalisé à partir de la base de données par certaines informations (comme le login ou l'id) stockées dans le principal ou
  • voulez apprendre comment a HandlerMethodArgumentResolverou WebArgumentResolverpeut résoudre cela de manière élégante, ou voulez simplement apprendre le contexte derrière @AuthenticationPrincipalet AuthenticationPrincipalArgumentResolver(parce qu'il est basé sur a HandlerMethodArgumentResolver)

puis continuez à lire - sinon utilisez simplement @AuthenticationPrincipalet merci à Rob Winch (auteur de @AuthenticationPrincipal) et Lukas Schmelzeisen (pour sa réponse).

(BTW: Ma réponse est un peu plus ancienne (janvier 2012), c'est donc Lukas Schmelzeisen qui est apparu comme le premier avec la @AuthenticationPrincipalsolution 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 moche car il pollue votre contrôleur avec des détails d'infrastructure, cela devrait normalement être caché par le framework.

Donc, ce que vous voudrez peut-être vraiment, c'est avoir un contrôleur comme celui-ci:

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

Par conséquent, il vous suffit de mettre en œuvre un WebArgumentResolver. Il a une méthode

Object resolveArgument(MethodParameter methodParameter,
                   NativeWebRequest webRequest)
                   throws Exception

Cela obtient la requête Web (deuxième paramètre) et doit renvoyer le User s'il se sent responsable de l'argument de la méthode (le premier paramètre).

Depuis le printemps 3.1, il existe un nouveau concept appelé HandlerMethodArgumentResolver. Si vous utilisez Spring 3.1+, vous devez 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 extraite du 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 vous 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 Spring MVC @Controller

Il convient de noter que si vous utilisez Spring 3.1, ils recommandent HandlerMethodArgumentResolver sur WebArgumentResolver. - voir le commentaire de Jay


La même chose avec HandlerMethodArgumentResolverpour 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>

@Voir Utilisation de l'interface Spring MVC 3.1 HandlerMethodArgumentResolver


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 la 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" @EnableWebMvcSecurity soit en enregistrant ce bean dans mvc:argument-resolvers- de la même manière que je l'ai décrit avec la solution Spring 3.1 ci-dessus.

@ Voir Spring Security 3.2 Reference, Chapter 11.2. @AuthenticationPrincipal


Solution Spring-Security 4.0

Cela fonctionne comme la solution Spring 3.2, mais dans Spring 4.0, @AuthenticationPrincipalet a AuthenticationPrincipalArgumentResolverété "déplacé" vers un autre package:

(Mais les anciennes classes dans ses 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 le ( org.springframework.security.web.method.annotation.) AuthenticationPrincipalArgumentResolver: soit en "activant", @EnableWebMvcSecuritysoit en enregistrant ce bean dans mvc:argument-resolvers- de la même manière 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>

@Voir Spring Security 5.0 Reference, Chapter 39.3 @AuthenticationPrincipal

Ralph
la source
Sinon, créez simplement un bean qui a une méthode getUserDetails () et @Autowire dans votre contrôleur.
sourcedelica
En implémentant cette solution, j'ai défini un point d'arrêt en haut de la fonction resolverArgument () mais mon application n'entre jamais dans le résolveur d'arguments Web. Votre configuration de ressort dans le contexte de servlet et non le contexte racine, non?
Jay
@Jay: Cette configuration fait partie du contexte racine, pas du contexte de servlet. - il semble que j'ai oublié de spécifier l'id (id="applicationConversionService")dans l'exemple
Ralph
11
Il convient de noter que si vous utilisez Spring 3.1, ils recommandent HandlerMethodArgumentResolver sur WebArgumentResolver. J'ai fait fonctionner HandlerMethodArgumentResolver en le configurant avec <annotation-driven> dans le contexte du servlet. À part cela, j'ai mis en œuvre la réponse telle que publiée ici et tout fonctionne très bien
Jay
@sourcedelica Pourquoi ne pas simplement créer une méthode statique?
Alex78191
66

Alors que Ralphs Answer fournit une solution élégante, avec Spring Security 3.2, vous n'avez plus besoin de mettre en œuvre la vôtre ArgumentResolver.

Si vous avez une UserDetailsimplémentation CustomUser, vous pouvez simplement faire ceci:

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

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

Voir la documentation sur la sécurité Spring: @AuthenticationPrincipal

Lukas Schmelzeisen
la source
2
pour ceux qui n'aiment pas lire les liens fournis, cela doit être activé soit par @EnableWebMvcSecurityou en XML:<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
sodik
Comment vérifier si le customUserest nul ou non?
Sajad
if (customUser != null) { ... }
T3rm1
27

Spring Security est conçu pour fonctionner avec d'autres frameworks non Spring, il n'est donc pas étroitement intégré à Spring MVC. Spring Security renvoie l' Authenticationobjet de la HttpServletRequest.getUserPrincipal()méthode par défaut, c'est donc ce que vous obtenez en tant que principal. Vous pouvez obtenir votre UserDetailsobjet directement à partir de celui-ci 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 de a UsernamePasswordAuthenticationToken, par exemple) et que le Authenticationne doit pas strictement contenir de UserDetails. Il peut s'agir d'une chaîne ou de tout autre type.

Si vous ne souhaitez pas appeler SecurityContextHolderdirectement, l'approche la plus élégante (que je suivrais) consiste à injecter votre propre interface d'accesseur de contexte de sécurité personnalisée qui est personnalisée pour correspondre à vos besoins et à vos types d'objet utilisateur. Créez une interface, avec les méthodes appropriées, par exemple:

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

Vous pouvez ensuite l'implémenter en accédant au SecurityContextHolderdans votre implémentation standard, découplant ainsi entièrement votre code de Spring Security. Ensuite, injectez-le dans les contrôleurs qui ont besoin d'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 soucier de remplir les locals des threads, etc.

Shaun le mouton
la source
J'envisageais cette approche, mais je n'étais pas sûr a) comment le faire exactement de la bonne façon (tm) et b) s'il y aurait des problèmes de filetage. Êtes-vous certain qu'il n'y aurait aucun problème là-bas? Je vais avec la méthode d'annotation publiée ci-dessus, mais je pense que c'est toujours une façon décente de s'y prendre. Merci d'avoir posté. :)
The Awnry Bear
4
C'est techniquement la même chose que d'accéder à SecurityContextHolder directement à partir de votre contrôleur, il ne devrait donc pas y avoir de problèmes de thread. Il garde simplement l'appel à un seul endroit et vous permet 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é.
Shaun the Sheep
Gotcha ... c'est ce que je pensais. Ce sera une bonne question sur laquelle revenir si j'ai des problèmes de test unitaire avec la méthode d'annotation, ou si je veux diminuer le couplage avec Spring.
The Awnry Bear
@LukeTaylor pouvez-vous s'il vous plaît expliquer plus en détail cette approche, je suis un peu nouveau dans la sécurité des ressorts et des ressorts, donc je ne comprends pas tout à fait comment mettre en œuvre cela. Où dois-je mettre en œuvre cela pour y accéder? à mon UserServiceImpl?
Cu7l4ss
9

Implémentez l' HandlerInterceptorinterface, puis injectez le UserDetailsdans chaque requête 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());
        }
}
un train
la source
1
Merci @atrain, c'est utile et élégant. De plus, j'ai dû ajouter le <mvc:interceptors>dans mon fichier de configuration d'application.
Eric
Il est préférable d'obtenir un utilisateur autorisé dans le modèle via spring-security-taglibs: stackoverflow.com/a/44373331/548473
Grigory Kislin
9

À partir de Spring Security version 3.2, la fonctionnalité personnalisée qui a été implémentée par certaines des réponses les plus anciennes, existe prête à l'emploi sous la forme de l' @AuthenticationPrincipalannotation qui est soutenue parAuthenticationPrincipalArgumentResolver .

Un exemple simple de son utilisation est:

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

CustomUser doit être assignable à partir de authentication.getPrincipal()

Voici les Javadocs correspondants de AuthenticationPrincipal et AuthenticationPrincipalArgumentResolver

geoand
la source
1
@nbro Quand j'ai ajouté la solution spécifique à la version, aucune des autres solutions n'avait été mise à jour pour prendre en compte cette solution
geoand
AuthenticationPrincipalArgumentResolver est maintenant obsolète
Igor Donin
5
@Controller
public abstract class AbstractController {
    @ModelAttribute("loggedUser")
    public User getLoggedUser() {
        return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}
Camarade
la source
0

Et si vous avez besoin d'un utilisateur autorisé dans les modèles (par exemple JSP), utilisez

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authentication property="principal.yourCustomField"/>

ensemble avec

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>${spring-security.version}</version>
    </dependency>
Grigory Kislin
la source
0

Vous pouvez essayer ceci: En utilisant l'objet d'authentification de Spring, nous pouvons obtenir les détails de l'utilisateur dans la méthode du contrôleur. Voici l'exemple, en passant l'objet d'authentification dans la méthode du contrôleur avec l'argument. Une fois que l'utilisateur est authentifié, les détails sont renseignés dans l'objet d'authentification.

@GetMapping(value = "/mappingEndPoint") <ReturnType> methodName(Authentication auth) {
   String userName = auth.getName(); 
   return <ReturnType>;
}
Mirza Shujathullah
la source
Veuillez préciser votre réponse.
Nikolai Shevchenko le
J'ai édité ma réponse, nous pourrions utiliser un objet d'authentification qui contient les détails de l'utilisateur qui s'est déjà authentifié.
Mirza Shujathullah du