Lors de L'utilisation de Spring Security, Quelle est la bonne façon d'obtenir des informations de nom d'utilisateur actuel (C'est-à-dire SecurityContext) dans un bean?

J'ai une application Web Spring MVC qui utilise Spring Security. Je veux savoir le nom d'utilisateur de l'utilisateur actuellement connecté. J'utilise l'extrait de code donné ci-dessous . Est-ce la façon acceptée?

Je n'aime pas avoir un appel à une méthode statique à l'intérieur de ce contrôleur - cela va à l'encontre de tout le but du printemps, à mon humble avis. Existe-t-il un moyen de configurer l'application pour que le SecurityContext actuel, ou L'authentification actuelle, soit injecté à la place?

  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView showResults(final HttpServletRequest request...) {
    final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
    ...
  }
265
demandé sur dur 2008-10-30 00:50:17

17 réponses

Si vous utilisez Printemps 3, le plus simple est:

 @RequestMapping(method = RequestMethod.GET)   
 public ModelAndView showResults(final HttpServletRequest request, Principal principal) {

     final String currentUser = principal.getName();

 }
239
répondu tsunade21 2016-05-29 12:10:15

Beaucoup de choses ont changé dans le monde du printemps depuis que cette question a été répondue. Le printemps a simplifié l'obtention de l'utilisateur actuel dans un contrôleur. Pour les autres haricots, Spring a adopté les suggestions de l'auteur et simplifié l'injection de "SecurityContextHolder". Plus de détails sont dans les commentaires.


C'est la solution avec laquelle j'ai fini par aller. Au lieu d'utiliser SecurityContextHolder dans mon contrôleur, je veux injecter quelque chose qui utilise SecurityContextHolder sous le capot mais élimine cela classe singleton de mon code. Je n'ai trouvé aucun moyen de le faire autre que de rouler ma propre interface, comme ceci:

public interface SecurityContextFacade {

  SecurityContext getContext();

  void setContext(SecurityContext securityContext);

}

Maintenant, mon contrôleur (ou N'importe quel POJO) ressemblerait à ceci:

public class FooController {

  private final SecurityContextFacade securityContextFacade;

  public FooController(SecurityContextFacade securityContextFacade) {
    this.securityContextFacade = securityContextFacade;
  }

  public void doSomething(){
    SecurityContext context = securityContextFacade.getContext();
    // do something w/ context
  }

}

Et, parce que l'interface est un point de découplage, les tests unitaires sont simples. Dans cet exemple, j'utilise Mockito:

public class FooControllerTest {

  private FooController controller;
  private SecurityContextFacade mockSecurityContextFacade;
  private SecurityContext mockSecurityContext;

  @Before
  public void setUp() throws Exception {
    mockSecurityContextFacade = mock(SecurityContextFacade.class);
    mockSecurityContext = mock(SecurityContext.class);
    stub(mockSecurityContextFacade.getContext()).toReturn(mockSecurityContext);
    controller = new FooController(mockSecurityContextFacade);
  }

  @Test
  public void testDoSomething() {
    controller.doSomething();
    verify(mockSecurityContextFacade).getContext();
  }

}

L'implémentation par défaut de l'interface ressemble à ceci:

public class SecurityContextHolderFacade implements SecurityContextFacade {

  public SecurityContext getContext() {
    return SecurityContextHolder.getContext();
  }

  public void setContext(SecurityContext securityContext) {
    SecurityContextHolder.setContext(securityContext);
  }

}

Et, enfin, la configuration du ressort de production ressemble à ce:

<bean id="myController" class="com.foo.FooController">
     ...
  <constructor-arg index="1">
    <bean class="com.foo.SecurityContextHolderFacade">
  </constructor-arg>
</bean>

Il semble plus qu'un peu stupide que Spring, un conteneur d'injection de dépendance de toutes choses, n'ait pas fourni de moyen d'injecter quelque chose de similaire. Je comprends que SecurityContextHolder a été hérité d'acegi, mais quand même. Le fait est qu'ils sont si proches - si seulement SecurityContextHolder avait un getter pour obtenir l'instance SecurityContextHolderStrategy sous-jacente (qui est une interface), vous pourriez l'injecter. En fait, j'ai même ouvert un problème JIRA à cet effet.

Une dernière chose-je viens de sensiblement j'ai changé la réponse que j'avais ici avant. Vérifiez l'historique si vous êtes curieux, mais, comme un collègue m'a fait remarquer, ma réponse précédente ne fonctionnerait pas dans un environnement multi-thread. Le sous-jacent SecurityContextHolderStrategy utilisé par SecurityContextHolder est, par défaut, une instance de ThreadLocalSecurityContextHolderStrategy, qui stocke SecurityContextdans ThreadLocal. Par conséquent, ce n'est pas nécessairement une bonne idée d'injecter le SecurityContext directement dans un bean au moment de l'initialisation - il peut être nécessaire de le récupérer à partir du ThreadLocal à chaque fois, dans un environnement multi-thread, de sorte que le bon est extrait.

55
répondu Scott Bale 2011-06-16 18:09:19

Je suis d'accord pour dire que le fait d'interroger SecurityContext pour l'utilisateur actuel pue, cela semble être une façon très décomplexée de gérer ce problème.

J'ai écrit une classe "helper" statique pour traiter ce problème; c'est sale en ce sens que c'est une méthode globale et statique, mais je me suis dit de cette façon si nous changeons quelque chose lié à la sécurité, au moins je n'ai qu'à changer les détails en un seul endroit:

/**
* Returns the domain User object for the currently logged in user, or null
* if no User is logged in.
* 
* @return User object for the currently logged in user, or null if no User
*         is logged in.
*/
public static User getCurrentUser() {

    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()

    if (principal instanceof MyUserDetails) return ((MyUserDetails) principal).getUser();

    // principal object is either null or represents anonymous user -
    // neither of which our domain User object can represent - so return null
    return null;
}


/**
 * Utility method to determine if the current user is logged in /
 * authenticated.
 * <p>
 * Equivalent of calling:
 * <p>
 * <code>getCurrentUser() != null</code>
 * 
 * @return if user is logged in
 */
public static boolean isLoggedIn() {
    return getCurrentUser() != null;
}
21
répondu matt b 2009-01-26 16:57:37

Pour qu'il apparaisse simplement dans vos pages JSP, vous pouvez utiliser la balise de sécurité Spring Lib:

Http://static.springsource.org/spring-security/site/docs/3.0.x/reference/taglibs.html

Pour utiliser l'une des balises, vous devez avoir le taglib de sécurité déclaré dans votre JSP:

<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

Ensuite, dans une page jsp, faites quelque chose comme ceci:

<security:authorize access="isAuthenticated()">
    logged in as <security:authentication property="principal.username" /> 
</security:authorize>

<security:authorize access="! isAuthenticated()">
    not logged in
</security:authorize>

NOTE: Comme mentionné dans les commentaires de @ SBerg413, vous devrez ajouter

Utilisez-les expressions="true"

À la balise "http" dans la sécurité.configuration xml pour que cela fonctionne.

21
répondu Brad Parks 2013-03-21 14:00:56

Je reçois l'utilisateur authentifié par HttpServletRequest.getUserPrincipal ();

Exemple:

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.support.RequestContext;

import foo.Form;

@Controller
@RequestMapping(value="/welcome")
public class IndexController {

    @RequestMapping(method=RequestMethod.GET)
    public String getCreateForm(Model model, HttpServletRequest request) {

        if(request.getUserPrincipal() != null) {
            String loginName = request.getUserPrincipal().getName();
            System.out.println("loginName : " + loginName );
        }

        model.addAttribute("form", new Form());
        return "welcome";
    }
}
13
répondu digz6666 2010-07-08 03:39:46

Si vous utilisez Spring Security ver > = 3.2, vous pouvez utiliser l'annotation @AuthenticationPrincipal:

@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(@AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) {
    String currentUsername = currentUser.getUsername();
    // ...
}

Ici CustomUser est un objet personnalisé qui implémente UserDetails, ce qui est retourné par une coutume UserDetailsService.

Vous trouverez plus d'informations dans le chapitre @AuthenticationPrincipal des documents de référence de sécurité Spring.

13
répondu matsev 2016-06-01 20:32:27

Au printemps 3 + vous avez les options suivantes.

Option 1:

@RequestMapping(method = RequestMethod.GET)    
public String currentUserNameByPrincipal(Principal principal) {
    return principal.getName();
}

Option 2:

@RequestMapping(method = RequestMethod.GET)
public String currentUserNameByAuthentication(Authentication authentication) {
    return authentication.getName();
}

Option 3:

@RequestMapping(method = RequestMethod.GET)    
public String currentUserByHTTPRequest(HttpServletRequest request) {
    return request.getUserPrincipal().getName();

}

Option 4: Fancy one: consultez ceci pour plus de détails

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
  ...
}
8
répondu Farm 2017-05-23 11:54:59

Oui, la statique est généralement mauvaise-généralement, mais dans ce cas, la statique est le code le plus sécurisé que vous pouvez écrire. Étant donné que le contexte de sécurité associe un Principal au thread en cours d'exécution, le code le plus sécurisé accéderait à la statique à partir du thread aussi directement que possible. Cacher l'accès derrière une classe wrapper qui est injectée fournit à un attaquant plus de points à attaquer. Ils n'auraient pas besoin d'accéder au code (qu'ils auraient du mal à changer si le jar était signé), ils ont juste besoin d'un moyen de remplacer la configuration, ce qui peut être fait à l'exécution ou glisser du XML sur le classpath. Même en utilisant l'injection d'annotation dans le code signé serait substituable avec XML externe. Un tel XML pourrait injecter le système en cours d'exécution avec un principal voyou. C'est probablement pourquoi Spring fait quelque chose de si non-Spring-like dans ce cas.

5
répondu Michael Bushe 2010-02-04 13:03:56

Je voudrais juste faire ceci:

request.getRemoteUser();
5
répondu Dan 2011-12-22 08:09:19

Pour la dernière application MVC du printemps que j'ai écrite, Je n'ai pas injecté le support SecurityContext, mais j'avais un contrôleur de base que j'avais deux méthodes utilitaires liées à cela ... isAuthenticated() & getUsername(). En interne, ils font l'appel de méthode statique que vous avez décrit.

Au moins, il n'est qu'une fois placé si vous avez besoin de refactoriser plus tard.

4
répondu RichH 2008-10-30 23:08:59

Vous pouvez utiliser Spring AOP aproach. Par exemple, si vous avez un service, cela doit connaître le principal actuel. Vous pouvez introduire une annotation personnalisée, c'est-à-dire @Principal , qui indique que ce Service doit dépendre du principal.

public class SomeService {
    private String principal;
    @Principal
    public setPrincipal(String principal){
        this.principal=principal;
    }
}

Ensuite, dans votre conseil, qui, je pense, doit étendre MethodBeforeAdvice, vérifiez que le service particulier a une annotation @ Principal et injectez le nom Principal, ou définissez-le sur 'anonyme' à la place.

3
répondu Pavel Rodionov 2008-10-31 13:03:48

Le seul problème est que même après l'authentification avec Spring Security, le bean utilisateur/principal n'existe pas dans le conteneur, donc l'injection de dépendance sera difficile. Avant D'utiliser Spring Security, nous créions un bean à portée de session qui avait le Principal actuel, l'injecter dans un "AuthService", puis injecter ce Service dans la plupart des autres services de l'Application. Donc, ces Services appelleraient simplement authService.getCurrentUser() pour obtenir l'objet. Si vous avez un placez dans votre code où vous obtenez une référence au même Principal dans la session, vous pouvez simplement le Définir comme une propriété sur votre bean de portée de session.

2
répondu cliff.meyers 2008-11-29 07:41:18

La meilleure solution si vous utilisez Spring 3 et avez besoin du principal authentifié dans votre contrôleur est de faire quelque chose comme ceci:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;

    @Controller
    public class KnoteController {
        @RequestMapping(method = RequestMethod.GET)
        public java.lang.String list(Model uiModel, UsernamePasswordAuthenticationToken authToken) {

            if (authToken instanceof UsernamePasswordAuthenticationToken) {
                user = (User) authToken.getPrincipal();
            }
            ...

    }
1
répondu Mark 2011-12-09 22:34:10

J'utilise l'annotation @AuthenticationPrincipal dans les classes @Controller ainsi que dans les classes annotées @ControllerAdvicer. Ex.:

@ControllerAdvice
public class ControllerAdvicer
{
    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class);


    @ModelAttribute("userActive")
    public UserActive currentUser(@AuthenticationPrincipal UserActive currentUser)
    {
        return currentUser;
    }
}

Où {[5] } est la classe que j'utilise pour les services des utilisateurs connectés, et s'étend de org.springframework.security.core.userdetails.User. Quelque chose comme:

public class UserActive extends org.springframework.security.core.userdetails.User
{

    private final User user;

    public UserActive(User user)
    {
        super(user.getUsername(), user.getPasswordHash(), user.getGrantedAuthorities());
        this.user = user;
    }

     //More functions
}

Vraiment facile.

1
répondu EliuX 2015-07-30 23:40:27

Essayez ceci

Authentification authentification = SecurityContextHolder.getContext ().getAuthentication ();
Chaîne userName = authentification.getName ();

0
répondu cherit 2011-11-18 09:00:36

Définissez Principal comme une dépendance dans votre méthode de contrôleur et spring injectera l'utilisateur authentifié actuel dans votre méthode lors de l'invocation.

0
répondu Imrank 2015-11-10 11:29:11

J'aime partager ma façon de soutenir les détails de l'utilisateur sur la page freemarker. Tout est très simple et fonctionne parfaitement!

Il vous suffit de placer L'authentification rerequest sur default-target-url (page après form-login) C'est ma méthode de contrôleur pour cette page:

@RequestMapping(value = "/monitoring", method = RequestMethod.GET)
public ModelAndView getMonitoringPage(Model model, final HttpServletRequest request) {
    showRequestLog("monitoring");


    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String userName = authentication.getName();
    //create a new session
    HttpSession session = request.getSession(true);
    session.setAttribute("username", userName);

    return new ModelAndView(catalogPath + "monitoring");
}

Et ceci est mon code ftl:

<@security.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER">
<p style="padding-right: 20px;">Logged in as ${username!"Anonymous" }</p>
</@security.authorize> 

Et c'est tout, le nom d'utilisateur apparaîtra sur chaque page après l'autorisation.

-1
répondu Serge 2013-10-20 11:46:17