web-dev-qa-db-fra.com

Comment obtenir les UserDetails de l'utilisateur actif

Dans mes contrôleurs, lorsque j'ai besoin de l'utilisateur actif (connecté), je procède comme suit pour obtenir mon implémentation UserDetails:

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

Cela fonctionne bien, mais je pense que le printemps pourrait rendre la vie plus facile dans un cas comme celui-ci. Existe-t-il un moyen d’avoir le UserDetails auto-câblé dans le contrôleur ou la méthode?

Par exemple, quelque chose comme:

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

Mais au lieu d’obtenir la UsernamePasswordAuthenticationToken, j’obtiens un UserDetails?

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

161
The Awnry Bear

Préambule: Depuis Spring-Security 3.2, il existe une annotation de Nice _@AuthenticationPrincipal_ décrite à la fin de cette réponse. C'est la meilleure façon de faire 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 à l’aide de certaines informations (comme le login ou l’id) stockées dans
  • veulent apprendre comment un HandlerMethodArgumentResolver ou WebArgumentResolver peut résoudre ce problème de manière élégante, ou simplement vouloir apprendre le contexte derrière _@AuthenticationPrincipal_ et AuthenticationPrincipalArgumentResolver (car il est basé sur un HandlerMethodArgumentResolver)

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

(BTW: Ma réponse est un peu plus ancienne (janvier 2012), donc elle était Lukas Schmelzeisen qui apparaît comme le premier avec la base de solution d'annotation _@AuthenticationPrincipal_ sur la sécurité du ressort 3.2.)


Ensuite, vous pouvez utiliser dans votre contrôleur

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

C'est ok si vous en avez besoin une fois. Mais si vous en avez besoin plusieurs fois, c'est moche, car cela pollue votre contrôleur avec des détails d'infrastructure, cela devrait normalement être caché par le framework.

Donc, ce que vous voulez peut-être vraiment, c'est d'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 récupère la requête Web (second paramètre) et doit renvoyer User s'il 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 l'utilisateur doit toujours être extraite du contexte de sécurité, mais ne constitue 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: Découvrez comment personnaliser les arguments de la méthode Spring MVC @Controller

Notez que si vous utilisez Spring 3.1, ils recommandent HandlerMethodArgumentResolver à WebArgumentResolver. - voir le commentaire de Jay


Idem 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 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 réponse de Lukas Schmelzeisen

C'est juste écrire

_ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
 }
_

Pour que cela fonctionne, vous devez enregistrer 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 Spring 3.1. solution ci-dessus.

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


Solution Spring-Security 4.0

Cela fonctionne comme la solution Spring 3.2, mais dans Spring 4.0, les _@AuthenticationPrincipal_ et AuthenticationPrincipalArgumentResolver ont été "déplacés" vers un autre package:

(Mais les anciennes classes de ses anciens paquets 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" _@EnableWebMvcSecurity_, soit en enregistrant ce bean dans _mvc:argument-resolvers_ - de la même manière que je l'ai décrit avec Spring 3.1. solution 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 de Spring Security 5.0, Chapitre 39.3 @AuthenticationPrincipal

214
Ralph

Alors que Ralphs Answer fournit une solution élégante, avec Spring Security 3.2, vous n’avez plus besoin de mettre en œuvre votre propre ArgumentResolver.

Si vous avez une implémentation UserDetailsCustomUser, vous pouvez simplement faire ceci:

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

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

Voir Documentation de sécurité Spring: @AuthenticationPrincipal

62

Spring Security est conçu pour fonctionner avec d'autres infrastructures que Spring, il n'est donc pas étroitement intégré à Spring MVC. Spring Security renvoie l'objet Authentication de la méthode HttpServletRequest.getUserPrincipal() par défaut, c'est donc ce que vous obtenez en tant que principal. Vous pouvez obtenir votre objet UserDetails directement à partir de celui-ci en utilisant

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

Notez également que les types d'objet peuvent varier en fonction du mécanisme d'authentification utilisé (vous pouvez ne pas obtenir un UsernamePasswordAuthenticationToken, par exemple) et le Authentication ne doit pas obligatoirement contenir un UserDetails. Ce peut être une chaîne ou tout autre type.

Si vous ne souhaitez pas appeler directement SecurityContextHolder, l'approche la plus élégante (que je suivrais) consiste à injecter votre propre interface d'accès au contexte de sécurité personnalisée, qui est personnalisée en fonction de vos besoins et des 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 implémenter cela en accédant à la variable SecurityContextHolder de votre implémentation standard, afin de découpler entièrement votre code de Spring Security. Puis injectez ceci dans les contrôleurs qui ont besoin d’avoir accès à des informations de sécurité ou à des informations sur l’utilisateur actuel.

L’autre avantage principal est qu’il est facile de réaliser des implémentations simples avec des données fixes à des fins de test, sans avoir à s’inquiéter de l’installation de locals de threads, etc.

27
Shaun the Sheep

Implémentez l'interface HandlerInterceptor, puis injectez le UserDetails dans chaque demande comportant 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());
        }
}
9
atrain

Depuis Spring Security version 3.2, les fonctionnalités personnalisées implémentées par certaines des réponses les plus anciennes existent immédiatement sous la forme de l'annotation @AuthenticationPrincipal gérée par AuthenticationPrincipalArgumentResolver.

Voici un exemple simple d'utilisation:

@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

8
geoand
@Controller
public abstract class AbstractController {
    @ModelAttribute("loggedUser")
    public User getLoggedUser() {
        return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}
5
Comrade

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>
0
GKislin