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

StackOverflow https://stackoverflow.com/questions/248562

Question

J'ai une application Web Spring MVC utilisant 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 annule tout 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();
    ...
  }
Était-ce utile?

La solution

Si vous utilisez Printemps 3 , le moyen le plus simple est:

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

     final String currentUser = principal.getName();

 }

Autres conseils

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 mais soustrait cette classe de type singleton à 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 un autre 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);
  }

}

Enfin, la configuration de production de 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 que ridicule que Spring, un conteneur d’injection de dépendance, 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 est qu’ils sont si proches: si seul SecurityContextHolder avait un getter pour obtenir l’instance SecurityContextHolderStrategy sous-jacente (qui est une interface), vous pouvez l’injecter. En fait, j'ai même ouvert un problème Jira à cet effet.

Une dernière chose - je viens de changer de manière substantielle la réponse que j'avais 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 SecurityContext dans un ThreadLocal . Par conséquent, il n’est pas forcément judicieux d’injecter SecurityContext directement dans un bean au moment de l’initialisation. Il peut être nécessaire de le récupérer à chaque fois dans ThreadLocal , -threaded, le bon est donc récupéré.

Je conviens qu'avoir à interroger le SecurityContext sur l'utilisateur actuel pue, cela semble être une manière très peu printanière de gérer ce problème.

J'ai écrit un statique "helper". classe pour faire face à ce problème; c’est sale en ce qu’il s’agit d’une méthode globale et statique, mais j’ai pensé de cette façon que si nous modifions quoi que ce soit lié à la sécurité, au moins je n’ai qu’à modifier les détails à un 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;
}

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" %>

Dans une page JSP, procédez comme suit:

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

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

REMARQUE: comme mentionné dans les commentaires de @ SBerg413, vous devez ajouter

.
  

use-expressions = " true "

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

Si vous utilisez la version Spring Security > = 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 qui est renvoyé par un personnalisé UserDetailsService .

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

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

Au printemps 3+, les options suivantes sont disponibles:

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: Une option intéressante: Consultez cette option pour plus de détails

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

Oui, la statique est généralement mauvaise - généralement, mais dans ce cas, la statique est le code le plus sûr que vous puissiez écrire. Etant 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 le plus directement 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 du printemps.

Je voudrais juste faire ceci:

request.getRemoteUser();

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 pour lequel j'avais deux méthodes utilitaires liées à cela ... isAuthenticated () & amp; getUsername (). En interne, ils effectuent l'appel de méthode statique que vous avez décrit.

Au moins, il ne se trouve qu’une fois à la place si vous avez besoin de refactoriser ultérieurement.

Vous pouvez utiliser l’approche Spring AOP. Par exemple, si vous avez un service, vous devez connaître le principal actuel. Vous pouvez introduire des annotations personnalisées, par exemple @Principal, qui indiquent 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, à mon avis, doit étendre MethodBeforeAdvice, vérifiez que le service en question possède l'annotation @Principal et injectez Nom principal, ou définissez-le sur "ANONYMOUS".

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, puis l'injections dans un "AuthService". puis injectez 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 où vous obtenez une référence au même principal dans la session, vous pouvez simplement le définir comme propriété de votre bean de niveau session.

Essayez ceci

  

Authentification Authentification =   SecurityContextHolder.getContext (). GetAuthentication ();

  String userName = authentication.getName ();

La meilleure solution si vous utilisez Spring 3 et que vous avez besoin du principal authentifié dans votre contrôleur est de procéder comme suit:

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();
            }
            ...

    }

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 enregistrés et s'étend à partir 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.

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

J'aime partager ma façon de prendre en charge les détails des utilisateurs sur la page freemarker. Tout est très simple et fonctionne parfaitement!

Il vous suffit de placer une nouvelle demande d'authentification sur default-target-url (page après la connexion à un formulaire). Voici ma méthode de contrôle 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 voici mon code ftl:

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

Et voilà, le nom d'utilisateur apparaîtra sur chaque page après autorisation.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top