web-dev-qa-db-fra.com

Lors de l’utilisation de Spring Security, quel est le moyen approprié d’obtenir des informations sur le nom d’utilisateur actuel (c’est-à-dire SecurityContext) dans un bean?

J'ai une application Web Spring MVC qui utilise Spring Security. Je souhaite connaître le nom d'utilisateur de l'utilisateur actuellement connecté. J'utilise l'extrait de code donné ci-dessous. Est-ce la manière acceptée?

Je n'aime pas avoir un appel à une méthode statique à l'intérieur de ce contrôleur - cela contrecarre le but de Spring, IMHO. Existe-t-il un moyen de configurer l'application pour que le SecurityContext ou l'authentification actuel soit injecté à la place?

  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView showResults(final HttpServletRequest request...) {
    final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
    ...
  }
275
Scott Bale

Si vous utilisez Spring , le moyen le plus simple est de:

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

     final String currentUser = principal.getName();

 }
247
tsunade21

Beaucoup de choses ont changé dans le monde du printemps depuis la réponse à cette question. Spring a simplifié l'accès de l'utilisateur actuel à 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 à laquelle j'ai fini par aller. Au lieu d'utiliser SecurityContextHolder dans mon contrôleur, je souhaite injecter quelque chose qui utilise SecurityContextHolder sous le capot, mais qui éloigne cette classe de type singleton de mon code. Je n'ai pas trouvé d'autre moyen de faire ceci 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, l'interface étant un point de découplage, le test unitaire est simple. 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, finalement, la configuration de production Spring ressemble à ceci:

<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 idiot que Spring, un conteneur d'injection de dépendance de toutes choses, n'ait pas fourni le moyen d'injecter quelque chose de similaire. Je comprends que SecurityContextHolder a été hérité de acegi, mais quand même. Le problème, c’est qu’ils sont si proches - si seulement SecurityContextHolder avait un getter pour obtenir l’instance sous-jacente SecurityContextHolderStrategy (qui est une interface), vous pouvez l’injecter. En fait, j'ai même a ouvert un numéro de Jira à cet effet.

Une dernière chose - je viens de changer de manière substantielle la réponse que j'avais ici auparavant. Consultez l'historique si vous êtes curieux, mais comme un collègue me l'a fait remarquer, ma réponse précédente ne fonctionnerait pas dans un environnement multithread. Le SecurityContextHolderStrategy sous-jacent utilisé par SecurityContextHolder est, par défaut, une instance de ThreadLocalSecurityContextHolderStrategy, qui stocke SecurityContexts dans un ThreadLocal. Par conséquent, il n’est pas forcément judicieux d’injecter le SecurityContext directement dans un bean au moment de l’initialisation. Il peut être nécessaire de le récupérer à chaque fois dans ThreadLocal, dans un environnement multithread, afin de récupérer le correct.

61
Scott Bale

Pour qu'il apparaisse dans vos pages JSP, vous pouvez utiliser la librairie de sécurité de printemps:

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

Pour utiliser l'une des balises, la balise de sécurité doit être déclarée 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

use-expressions = "true"

à la balise "http" dans la configuration security.xml pour que cela fonctionne.

21
Brad Parks

Je conviens qu'avoir à interroger SecurityContext sur l'utilisateur actuel pue, cela semble être une façon très simple de gérer ce problème.

J'ai écrit une classe statique "d'aide" pour traiter ce problème; c'est sale en ce que c'est une méthode globale et statique, mais j'ai pensé de cette façon que si nous modifions quoi que ce soit en rapport avec la sécurité, au moins il me suffit de 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
matt b

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é implémentant UserDetails qui est renvoyé par un objet personnalisé UserDetailsService.

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

14
matsev

Je reçois un 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
digz6666

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: Fantaisie un: Consultez cette option pour plus de détails

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

Oui, la statique est généralement mauvaise - en général, mais dans ce cas, la statique est le code le plus sûr que vous puissiez é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 injectée fournit à un attaquant plus de points à attaquer. Ils n'auraient pas besoin d'accéder au code (qu'ils auraient beaucoup de difficulté à changer si le fichier jar était signé), ils ont simplement besoin d'un moyen de remplacer la configuration, ce qui peut être fait à l'exécution ou en glissant du code XML sur le chemin de classe. Même en utilisant l'injection d'annotation dans le code signé, il serait possible de remplacer le XML externe. Un tel XML pourrait injecter au système en fonctionnement un principal non autorisé. C’est probablement pour cette raison que Spring fait quelque chose de si différent de Spring.

5
Michael Bushe

Je voudrais juste faire ceci:

request.getRemoteUser();
5
Dan

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

Au moins, ce n'est qu'une seule fois si vous avez besoin de refactoriser ultérieurement.

4
RichH

Vous pouvez utiliser le printemps AOP aproach. Par exemple, si vous avez un service, vous devez connaître le principal actuel. Vous pouvez introduire une annotation personnalisée, par exemple @Principal, qui indique que ce service doit être dépendant du principal.

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

Ensuite, dans votre conseil, qui devrait, à mon avis, étendre MethodBeforeAdvice, vérifiez que le service concerné possède l'annotation @Principal et injectez Nom principal, ou définissez-le sur "ANONYME".

3
Pavel Rodionov

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, il sera donc difficile de l'injecter par dépendance. Avant d'utiliser Spring Security, nous créions un bean de session comportant le principal actuel, l'injections dans un "AuthService", puis ce service dans la plupart des autres services de l'application. Ainsi, ces services appellent simplement authService.getCurrentUser () pour obtenir l'objet. Si votre code contient un emplacement dans lequel vous obtenez une référence au même principal dans la session, vous pouvez simplement le définir en tant que propriété sur votre bean de niveau session.

2
cliff.meyers

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;
    }
}

UserActive 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
EliuX

La meilleure solution si vous utilisez Spring 3 et que vous avez besoin du principal authentifié dans votre contrôleur est de procéder de la manière suivante:

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
Mark

Essaye ça

Authentification Authentification = SecurityContextHolder.getContext (). GetAuthentication ();
String userName = authentication.getName ();

1
cherit

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

0
Imrank