Al usar Spring Security, ¿cuál es la forma correcta de obtener información de usuario actual (es decir, SecurityContext) en un bean?

Tengo una aplicación web Spring MVC que usa Spring Security. Quiero saber el nombre de usuario del usuario actualmente conectado. Estoy usando el fragmento de código que figura a continuación. ¿Es esta la forma aceptada?

No me gusta tener una llamada a un método estático dentro de este controlador, que frustra todo el propósito de Spring, en mi humilde opinión. ¿Hay alguna manera de configurar la aplicación para que se inyecte el SecurityContext actual o la Autenticación actual?

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

Si está utilizando Spring 3 , la forma más fácil es:

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

Mucho ha cambiado en el mundo de la spring desde que se respondió esta pregunta. Spring ha simplificado la obtención del usuario actual en un controlador. Para otros beans, Spring ha adoptado las sugerencias del autor y simplificado la inyección de ‘SecurityContextHolder’. Más detalles están en los comentarios.


Esta es la solución con la que he terminado. En lugar de usar SecurityContextHolder en mi controlador, quiero inyectar algo que use SecurityContextHolder debajo del capó pero abstrae esa clase de singleton de mi código. No he encontrado otra manera de hacer esto que no sea rodar mi propia interfaz, así:

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

Ahora, mi controlador (o lo que sea POJO) se vería así:

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

Y, debido a que la interfaz es un punto de desacoplamiento, las pruebas unitarias son sencillas. En este ejemplo, utilizo 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(); } } 

La implementación predeterminada de la interfaz se ve así:

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

Y, finalmente, la producción Spring config se ve así:

  ...     

Parece más que un poco tonto que Spring, un contenedor de dependency injection de todas las cosas, no haya suministrado una forma de inyectar algo similar. Entiendo que SecurityContextHolder fue heredado de acegi, pero aún así. La cuestión es que están muy cerca, si solo SecurityContextHolder tuviera un getter para obtener la instancia subyacente de SecurityContextHolderStrategy (que es una interfaz), podrías inyectar eso. De hecho, incluso abrí una edición de Jira a tal efecto.

Una última cosa: he cambiado sustancialmente la respuesta que tenía aquí antes. Verifique la historia si tiene curiosidad, pero, como me señaló un compañero de trabajo, mi respuesta anterior no funcionaría en un entorno de subprocesos múltiples. La SecurityContextHolderStrategy subyacente utilizada por SecurityContextHolder es, de forma predeterminada, una instancia de ThreadLocalSecurityContextHolderStrategy , que almacena SecurityContext s en un ThreadLocal . Por lo tanto, no es necesariamente una buena idea inyectar SecurityContext directamente en un bean en el momento de la inicialización; es posible que deba recuperarse de ThreadLocal cada vez, en un entorno de subprocesos múltiples, para que se recupere el correcto.

Estoy de acuerdo en que tener que consultar el SecurityContext para el usuario actual apesta, parece una manera muy poco segura para manejar este problema.

Escribí una clase de “ayudante” estática para tratar este problema; está sucio porque es un método global y estático, pero pensé que si cambiáramos algo relacionado con la Seguridad, al menos solo tengo que cambiar los detalles en un solo lugar:

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

Para que aparezca en sus páginas JSP, puede usar Spring Security Tag Lib:

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

Para usar cualquiera de las tags, debe tener el taglib de seguridad declarado en su JSP:

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

Luego, en una página jsp, haz algo como esto:

  logged in as    not logged in  

NOTA: Como se menciona en los comentarios de @ SBerg413, deberá agregar

use-expressions = “true”

a la etiqueta “http” en la configuración security.xml para que esto funcione.

Obtuve el usuario autenticado por HttpServletRequest.getUserPrincipal ();

Ejemplo:

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

Si está utilizando Spring Security ver> = 3.2, puede usar la anotación @AuthenticationPrincipal :

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

Aquí, CustomUser es un objeto personalizado que implementa UserDetails que es devuelto por un UserDetailsService personalizado.

Se puede encontrar más información en el capítulo @AuthenticationPrincipal de los documentos de referencia de Spring Security.

En la spring 3+ tienes las siguientes opciones.

Opción 1 :

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

Opcion 2 :

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

Opción 3:

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

Opción 4: Fancy one: mira esto para más detalles

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

Sí, la estática generalmente es mala, en general, pero en este caso, la estática es el código más seguro que puedes escribir. Dado que el contexto de seguridad asocia un Principal con el hilo que se está ejecutando actualmente, el código más seguro accedería a la estática del hilo lo más directamente posible. Ocultar el acceso detrás de una clase de contenedor que se inyecta proporciona a un atacante más puntos para atacar. No necesitarían acceder al código (lo cual les sería difícil cambiar si el contenedor estuviera firmado), solo necesitan una forma de anular la configuración, lo que se puede hacer en tiempo de ejecución o deslizando algunos XML en el classpath. Incluso el uso de inyección de anotación en el código firmado sería reemplazable con XML externo. Tal XML podría inyectar el sistema en ejecución con un principal deshonesto. Esta es probablemente la razón por la cual Spring está haciendo algo tan poco parecido a Spring en este caso.

Yo solo haría esto:

 request.getRemoteUser(); 

Para la última aplicación de Spring MVC que escribí, no inyecté el titular de SecurityContext, pero sí tenía un controlador base que tenía dos métodos de utilidad relacionados con esto … isAuthenticated () y getUsername (). Internamente hacen la llamada al método estático que describiste.

Al menos entonces solo en un lugar si necesitas refactorizar más tarde.

Podría usar Spring AOP approach. Por ejemplo, si tiene algún servicio, necesita saber el principal actual. Puede introducir una anotación personalizada, es decir, @Principal, que indica que este Servicio debe ser dependiente del director.

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

Luego, en su consejo, que creo que necesita extender MethodBeforeAdvice, verifique que el servicio particular tenga la anotación @Principal e inyecte el nombre principal, o configúrelo a ‘ANONYMOUS’ en su lugar.

El único problema es que incluso después de autenticarse con Spring Security, el usuario / principal bean no existe en el contenedor, por lo que la inyección de la dependencia será difícil. Antes de usar Spring Security creábamos un bean con ámbito de sesión que tenía el Principal actual, lo inyectamos en un “AuthService” y luego insertamos ese Servicio en la mayoría de los otros servicios en la Aplicación. Entonces esos Servicios simplemente llamarían a authService.getCurrentUser () para obtener el objeto. Si tiene un lugar en su código donde obtiene una referencia al mismo Principal en la sesión, simplemente puede establecerlo como una propiedad en su bean con ámbito de sesión.

La mejor solución si está utilizando Spring 3 y necesita el principal autenticado en su controlador es hacer algo como esto:

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

Estoy usando la anotación @AuthenticationPrincipal en @Controller clases @Controller , así como en @ControllerAdvicer anotados. Ex.:

 @ControllerAdvice public class ControllerAdvicer { private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class); @ModelAttribute("userActive") public UserActive currentUser(@AuthenticationPrincipal UserActive currentUser) { return currentUser; } } 

Donde UserActive es la clase que uso para los servicios de los usuarios registrados, y se extiende desde org.springframework.security.core.userdetails.User . Algo como:

 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 } 

Realmente fácil.

Prueba esto

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

Defina Principal como una dependencia en su método de controlador y el resorte inyectará al usuario autenticado actual en su método en la invocación.

Me gusta compartir mi forma de apoyar los detalles del usuario en la página de freemarker. ¡Todo es muy simple y funciona perfectamente!

Solo tiene que colocar la nueva solicitud de Autenticación en default-target-url (página después del inicio de sesión de formulario) Este es mi método de Controler para esa página:

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

Y este es mi código ftl:

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

Logged in as ${username!"Anonymous" }

Y eso es todo, el nombre de usuario aparecerá en cada página después de la autorización.