tutorial manager example current authenticated after java spring spring-mvc spring-security

java - manager - spring security get username after login



Cuando se utiliza Spring Security, ¿cuál es la forma correcta de obtener la información del nombre de usuario actual(es decir, SecurityContext) en un bean? (17)

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

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

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

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


El único problema es que incluso después de autenticarse con Spring Security, el usuario / bean principal no existe en el contenedor, por lo que la inyección de dependencia será difícil. Antes de utilizar Spring Security, crearíamos un bean de ámbito de sesión que tenía el Principal actual, inyectarlo en un "Servicio de autenticación" y luego inyectar ese Servicio en la mayoría de los otros servicios de la Aplicación. Así que 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 configurarlo como una propiedad en su bean de ámbito de sesión.


En 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) { ... }


Estoy de acuerdo en que tener que consultar el SecurityContext para el usuario actual apesta, parece una forma muy difícil de manejar este problema.

Escribí una clase "auxiliar" estática para tratar este problema; está sucio porque es un método global y estático, pero pensé de esta manera si cambiamos algo relacionado con la Seguridad, al menos solo tengo que cambiar los detalles en un 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. * <p> * Equivalent of calling: * <p> * <code>getCurrentUser() != null</code> * * @return if user is logged in */ public static boolean isLoggedIn() { return getCurrentUser() != null; }


Estoy usando la anotación @AuthenticationPrincipal en @Controller clases de @Controller y también en @ControllerAdvicer anotadas en @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; } }

Donde UserActive es la clase que uso para los servicios de 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.


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


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

Solo debe colocar la 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"> <p style="padding-right: 20px;">Logged in as ${username!"Anonymous" }</p> </@security.authorize>

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


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

Esta es la solución con la que terminé yendo. En lugar de usar SecurityContextHolder en mi controlador, quiero inyectar algo que use SecurityContextHolder bajo el capó, pero aleje esa clase de código único de mi código. No he encontrado otra forma 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, dado que la interfaz es un punto de desacoplamiento, la prueba de la unidad es sencilla. 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í:

<bean id="myController" class="com.foo.FooController"> ... <constructor-arg index="1"> <bean class="com.foo.SecurityContextHolderFacade"> </constructor-arg> </bean>

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

Una última cosa: acabo de cambiar sustancialmente la respuesta que tenía aquí antes. Verifique el historial si tiene curiosidad pero, como me lo 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 múltiples subprocesos, por lo que se recupera el correcto.


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


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

Por lo menos, entonces solo está en un lugar si necesita refactorizar más tarde.


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

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

Para usar cualquiera de las etiquetas, 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, haga algo como esto:

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

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.


Prueba esto

Autenticación de autenticación = SecurityContextHolder.getContext (). GetAuthentication ();
String userName = authentication.getName ();


Sí, las estadísticas están mal, generalmente, pero en este caso, la estática es el código más seguro que puede escribir. Dado que el contexto de seguridad asocia un Principal con el hilo actualmente en ejecución, el código más seguro accederá a la estática desde el hilo tan directamente como sea posible. Ocultar el acceso detrás de una clase de envoltorio que se inyecta proporciona a un atacante más puntos para atacar. No necesitarían acceso al código (al cual les sería difícil cambiar si se firmara el archivo jar), solo necesitan una forma de anular la configuración, que se puede hacer en tiempo de ejecución o deslizando algunos XML en el classpath. Incluso el uso de la inyección de anotación en el código firmado sería reemplazable con XML externo. Dicho XML podría inyectar el sistema en ejecución con un principal deshonesto. Probablemente esta sea la razón por la que Spring está haciendo algo tan poco parecido a Spring en este caso.


Si está usando 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.

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


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


Usted podría utilizar el enfoque de primavera AOP. Por ejemplo, si tiene algún servicio, debe conocer el principal actual. Podría introducir una anotación personalizada, es decir, @Principal, que indica que este Servicio debe ser principal dependiente.

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

Luego, en su consejo, que creo que necesita extender MethodBeforeAdvice, verificar que el servicio en particular tenga la anotación @Principal e inyectar el nombre del director, o establecerlo en "ANÓNIMO".


Yo solo haría esto:

request.getRemoteUser();