Lors de l’utilisation de Spring Security, quelle est la bonne façon d’obtenir des informations sur le nom d’utilisateur actuel (c.-à-d. SecurityContext) dans un bean?

J’ai une application Web Spring MVC qui utilise Spring Security. Je veux connaître le nom d’utilisateur de l’utilisateur actuellement connecté. J’utilise l’extrait de code ci-dessous. Est-ce la voie acceptée?

Je n’aime pas avoir un appel à une méthode statique à l’intérieur de ce contrôleur – ce qui va à l’encontre du but de Spring, IMHO. Existe-t-il un moyen de configurer l’application pour qu’elle injecte le SecurityContext actuel ou l’authentification actuelle?

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

Si vous utilisez Spring 3 , le plus simple est:

  @RequestMapping(method = RequestMethod.GET) public ModelAndView showResults(final HttpServletRequest request, Principal principal) { final Ssortingng currentUser = principal.getName(); } 

Beaucoup de choses ont changé dans le monde du spring depuis la réponse à cette question. Spring a simplifié l’obtention de l’utilisateur actuel dans un contrôleur. Pour les autres beans, 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 cette classe singleton de mon code. Je n’ai trouvé aucun moyen de le faire autrement que de déployer ma propre interface, comme ceci:

 public interface SecurityContextFacade { SecurityContext getContext(); void setContext(SecurityContext securityContext); } 

Maintenant, mon contrôleur (ou tout 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, étant donné que l’interface est un sharepoint 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 config de production Spring ressemble à ceci:

  ...     

Il semble plus qu’un peu idiot que Spring, un contenant d’dependency injection de toutes les choses, n’ait pas fourni le moyen d’injecter quelque chose de semblable. Je comprends que SecurityContextHolder été hérité de acegi, mais toujours. Le fait est qu’ils sont si proches – si seul SecurityContextHolder avait un getter pour obtenir l’instance SecurityContextHolderStrategy sous-jacente (qui est une interface), vous pourriez injecter cela. En fait, j’ai même ouvert un numéro Jira à cet effet.

Une dernière chose – je viens de changer de manière substantielle la réponse que j’avais ici auparavant. 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 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 nécessairement 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 un environnement multithread, afin d’en récupérer le bon.

Je suis d’accord que le fait de devoir interroger le SecurityContext pour l’utilisateur actuel pue, cela semble être un moyen très simple de résoudre ce problème.

J’ai écrit une classe “helper” statique pour résoudre ce problème; c’est sale en ce sens que c’est une méthode globale et statique, mais j’ai pensé que si nous modifions quelque chose lié à la sécurité, au moins je n’aurais 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. * 

* Equivalent of calling: *

* getCurrentUser() != null * * @return if user is logged in */ public static boolean isLoggedIn() { return getCurrentUser() != null; }

Pour que cela apparaisse simplement dans vos pages JSP, vous pouvez utiliser la balise de sécurité Spring:

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

Pour utiliser l’un des tags, 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:

  logged in as    not logged in  

REMARQUE: Comme mentionné dans les commentaires de @ SBerg413, vous devrez append

use-expressions = “true”

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

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 Ssortingng getCreateForm(Model model, HttpServletRequest request) { if(request.getUserPrincipal() != null) { Ssortingng loginName = request.getUserPrincipal().getName(); System.out.println("loginName : " + loginName ); } model.addAtsortingbute("form", new Form()); return "welcome"; } } 

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) { Ssortingng currentUsername = currentUser.getUsername(); // ... } 

Ici, CustomUser est un object personnalisé qui implémente UserDetails qui est renvoyé par un UserDetailsService personnalisé.

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

Au spring 3+, vous avez les options suivantes.

Option 1 :

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

Option 2 :

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

Option 3:

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

Option 4: Fancy one: Découvrez-le 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, le code statique est le code le plus sécurisé 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ède à l’élément statique du thread aussi directement que possible. Cacher l’access derrière une classe d’encapsulation 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 fichier jar était signé), ils ont juste besoin d’un moyen de remplacer la configuration, ce qui peut être fait au moment de l’exécution ou du XML sur le classpath. Même l’utilisation de l’injection d’annotation dans le code signé serait remplaçable avec le code XML externe. Un tel XML pourrait injecter le système en cours avec un principal non autorisé. C’est probablement pour cette raison que Spring fait quelque chose d’aussi peu printanier dans ce cas.

Je voudrais juste faire ça:

 request.getRemoteUser(); 

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

Au moins, c’est seulement en une fois si vous avez besoin de refacturer plus tard.

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

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

Ensuite, dans votre conseil, qui doit selon moi étendre MethodBeforeAdvice, vérifiez que ce service particulier a une annotation @Principal et injecte le nom Principal, ou définissez-le à la place «ANONYMOUS».

Le seul problème est que, même après l’authentification avec Spring Security, le bean user / principal n’existe pas dans le conteneur, il sera donc difficile d’injecter des dépendances. Avant que nous utilisions Spring Security, nous créions un bean de session ayant le principal actuel, l’injections dans un “AuthService” et injections ensuite ce service dans la plupart des autres services de l’application. Donc, ces services appellent simplement authService.getCurrentUser () pour obtenir l’object. Si vous avez une place dans votre code où vous obtenez une référence au même principal dans la session, vous pouvez simplement la définir comme propriété sur votre bean de scope de session.

La meilleure solution si vous utilisez Spring 3 et que vous 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.Ssortingng 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 @ControllerAdvicer annotées @ControllerAdvicer . Ex.:

 @ControllerAdvice public class ControllerAdvicer { private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class); @ModelAtsortingbute("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 depuis 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.

Essaye ça

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

Définissez Principal comme dépendance dans votre méthode de contrôleur et Spring injectera l’utilisateur authentifié actuel dans votre méthode lors de l’appel.

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

Il vous suffit de placer la requête d’authentification sur default-target-url (page après form-login) Ceci est 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(); Ssortingng userName = authentication.getName(); //create a new session HttpSession session = request.getSession(true); session.setAtsortingbute("username", userName); return new ModelAndView(catalogPath + "monitoring"); } 

Et ceci est mon code ftl:

 <@security.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER"> 

Logged in as ${username!"Anonymous" }

Et ça y est, le nom d’utilisateur apparaîtra sur chaque page après autorisation.