Quando si utilizza Spring Security, qual è il modo corretto per ottenere le informazioni sul nome utente corrente (es. SecurityContext) in un bean?

Ho un’applicazione web Spring MVC che utilizza Spring Security. Voglio conoscere il nome utente dell’utente attualmente connesso. Sto usando lo snippet di codice indicato di seguito. È questo il modo accettato?

Non mi piace avere una chiamata a un metodo statico all’interno di questo controller, che sconfigge l’intero scopo di Spring, IMHO. C’è un modo per configurare l’app in modo che venga iniettato l’attuale SecurityContext o l’attuale Autenticazione?

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

Se stai usando Spring 3 , il modo più semplice è:

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

Molto è cambiato nel mondo primaverile da quando è stata data una risposta a questa domanda. La spring è stata semplificata ottenendo l’utente corrente in un controller. Per gli altri fagioli, Spring ha adottato i suggerimenti dell’autore e ha semplificato l’iniezione di “SecurityContextHolder”. Maggiori dettagli sono nei commenti.


Questa è la soluzione con cui ho finito. Invece di usare SecurityContextHolder nel mio controller, voglio iniettare qualcosa che usa SecurityContextHolder sotto il cofano ma astrae quella class simile al singleton dal mio codice. Non ho trovato altro modo di fare questo oltre a far ruotare la mia interfaccia, in questo modo:

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

Ora, il mio controller (o qualunque POJO) sarebbe simile a questo:

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

E, poiché l’interfaccia è un punto di disaccoppiamento, il test dell’unità è semplice. In questo esempio utilizzo 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’implementazione predefinita dell’interfaccia è simile a questa:

 public class SecurityContextHolderFacade implements SecurityContextFacade { public SecurityContext getContext() { return SecurityContextHolder.getContext(); } public void setContext(SecurityContext securityContext) { SecurityContextHolder.setContext(securityContext); } } 

E, infine, la configurazione Spring di produzione si presenta così:

  ...     

Sembra più che un po ‘sciocco che Spring, un contenitore per le iniezioni di dipendenza di tutte le cose, non abbia fornito un modo per iniettare qualcosa di simile. Capisco che SecurityContextHolder stato ereditato da acegi, ma ancora. Il fatto è che sono così vicini – se solo SecurityContextHolder avesse un getter per ottenere l’istanza SecurityContextHolderStrategy sottostante (che è un’interfaccia), è ansible inserirla. In effetti, ho persino aperto un problema con Jira in tal senso.

Un’ultima cosa: ho sostanzialmente cambiato la risposta che avevo qui prima. Controlla la cronologia se sei curioso ma, come mi ha indicato un collega, la mia risposta precedente non funzionava in un ambiente multi-thread. La SecurityContextHolderStrategy sottostante utilizzata da SecurityContextHolder è, per impostazione predefinita, un’istanza di ThreadLocalSecurityContextHolderStrategy , che memorizza SecurityContext in un ThreadLocal . Pertanto, non è necessariamente una buona idea iniettare SecurityContext direttamente in un bean al momento dell’inizializzazione: potrebbe dover essere recuperato ogni volta da ThreadLocal , in un ambiente a più thread, in modo da recuperare quello corretto.

Sono d’accordo sul fatto che dover interrogare SecurityContext per l’utente corrente puzza, sembra un modo molto inedito per gestire questo problema.

Ho scritto una class “helper” statica per affrontare questo problema; è sporco in quanto è un metodo globale e statico, ma ho pensato in questo modo se cambiamo qualsiasi cosa relativa alla sicurezza, almeno devo solo cambiare i dettagli in un unico punto:

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

Per visualizzarlo nelle tue pagine JSP, puoi utilizzare il tag di sicurezza Spring Lib:

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

Per utilizzare uno qualsiasi dei tag, devi avere i tagli di sicurezza dichiarati nel tuo JSP:

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

Quindi in una pagina jsp fai qualcosa di simile a questo:

  logged in as    not logged in  

NOTA: come indicato nei commenti di @ SBerg413, dovrai aggiungere

utilizzare espressioni = “true”

al tag “http” nella configurazione security.xml affinché funzioni.

Ottengo l’utente autenticato da HttpServletRequest.getUserPrincipal ();

Esempio:

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

Se si utilizza Spring Security ver> = 3.2, è ansible utilizzare l’annotazione @AuthenticationPrincipal :

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

Qui, CustomUser è un object personalizzato che implementa UserDetails che viene restituito da un UserDetailsService personalizzato.

Ulteriori informazioni sono disponibili nel capitolo @AuthenticationPrincipal dei documenti di riferimento di Spring Security.

Nella spring 3+ hai le seguenti opzioni.

Opzione 1 :

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

Opzione 2 :

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

Opzione 3:

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

Opzione 4: Fancy one: controlla questo per ulteriori dettagli

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

Sì, le statiche sono in genere cattive, in genere, ma in questo caso, la statica è il codice più sicuro che puoi scrivere. Poiché il contesto di sicurezza associa un preside al thread attualmente in esecuzione, il codice più sicuro accederà allo statico dalla discussione il più direttamente ansible. Nascondere l’accesso dietro una class wrapper che viene iniettata fornisce a un utente malintenzionato più punti da attaccare. Non avrebbero bisogno di accedere al codice (che avrebbe difficoltà a cambiare se il barattolo fosse firmato), hanno solo bisogno di un modo per sovrascrivere la configurazione, che può essere fatto in fase di runtime o di slittare alcuni XML sul classpath. Anche l’uso dell’annotazione di annotazione nel codice firmato potrebbe essere sovrascrivibile con XML esterno. Tale XML potrebbe iniettare il sistema in esecuzione con un preside canaglia. Questo è probabilmente il motivo per cui Spring in questo caso sta facendo qualcosa di non simile a Spring.

Vorrei solo fare questo:

 request.getRemoteUser(); 

Per l’ultima app Spring MVC che ho scritto, non ho iniettato il titolare di SecurityContext, ma avevo un controller di base con due metodi di utilità correlati a questo … isAuthenticated () e getUsername (). Internamente fanno la chiamata al metodo statico che hai descritto.

Almeno allora è solo in una volta posto se hai bisogno di un refactoring successivo.

Potresti usare l’aproach di Spring AOP. Ad esempio, se si dispone di un servizio, è necessario conoscere l’entity framework corrente. È ansible introdurre annotazioni personalizzate, ad esempio @Principal, che indicano che questo servizio deve essere dipendente dalle principali.

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

Quindi, nel tuo consiglio, che penso debba estendere MethodBeforeAdvice, controlla che quel particolare servizio abbia annotazione @Principal e inietti il ​​nome del Principal, o lo imposti invece su “ANONYMOUS”.

L’unico problema è che anche dopo l’autenticazione con Spring Security, il bean utente / principale non esiste nel contenitore, quindi l’iniezione di dipendenza sarà difficile. Prima di utilizzare Spring Security, creeremmo un bean con scope di sessione che aveva l’attuale Principal, lo inseriva in un “AuthService” e poi lo inietta nella maggior parte degli altri servizi nell’applicazione. Quindi questi servizi chiamerebbero semplicemente authService.getCurrentUser () per ottenere l’object. Se si dispone di un posto nel codice in cui si ottiene un riferimento allo stesso preside nella sessione, è sufficiente impostarlo come una proprietà sul bean con ambito sessione.

La soluzione migliore se usi Spring 3 e hai bisogno del principal autenticato nel controller è di fare qualcosa del genere:

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

Sto usando l’annotazione @AuthenticationPrincipal nelle classi @Controller e nelle annotazioni @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; } } 

Dove UserActive è la class che utilizzo per i servizi degli utenti registrati e si estende da org.springframework.security.core.userdetails.User . Qualcosa di simile a:

 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 } 

Davvero facile.

Prova questo

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

Definisci Principal come dipendenza dal tuo metodo di controllo e spring invierà l’utente autenticato corrente nel tuo metodo al richiamo.

Mi piace condividere il mio modo di supportare i dettagli dell’utente nella pagina di freemarker. Tutto è molto semplice e funziona perfettamente!

Devi solo inserire la richiesta di autenticazione su default-target-url (pagina dopo form-login) Questo è il mio metodo Controler per quella pagina:

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

E questo è il mio codice ftl:

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

Logged in as ${username!"Anonymous" }

E questo è tutto, il nome utente apparirà su ogni pagina dopo l’authorization.