Cómo obtener UserDetails de usuario activo

En mis controladores, cuando necesito el usuario activo (conectado), hago lo siguiente para obtener mi implementación de UserDetails :

 User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); log.debug(activeUser.getSomeCustomField()); 

Funciona bien, pero creo que Spring podría hacer la vida más fácil en un caso como este. ¿Hay alguna forma de que el UserDetails autoconecte en el controlador o el método?

Por ejemplo, algo como:

 public ModelAndView someRequestHandler(Principal principal) { ... } 

Pero en lugar de obtener el UsernamePasswordAuthenticationToken , ¿obtengo un UserDetails en UserDetails lugar?

Estoy buscando una solución elegante. ¿Algunas ideas?

Preámbulo: desde Spring-Security 3.2 hay una bonita anotación @AuthenticationPrincipal descrita al final de esta respuesta. Este es el mejor camino a seguir cuando usa Spring-Security> = 3.2.

Cuando tú:

  • use una versión anterior de Spring-Security,
  • necesita cargar su Objeto de Usuario personalizado de la Base de Datos por alguna información (como el nombre de usuario o ID) almacenada en el principal o
  • desea aprender cómo un HandlerMethodArgumentResolver o WebArgumentResolver puede resolver esto de una manera elegante, o simplemente quiere aprender los antecedentes detrás de @AuthenticationPrincipal y AuthenticationPrincipalArgumentResolver (porque está basado en HandlerMethodArgumentResolver )

luego continúe leyendo; de lo contrario, utilice @AuthenticationPrincipal y gracias a Rob Winch (autor de @AuthenticationPrincipal ) y Lukas Schmelzeisen (por su respuesta).

(Por cierto: mi respuesta es un poco más antigua (enero de 2012), por lo que fue Lukas Schmelzeisen quien apareció como el primero con la base de la solución de anotación @AuthenticationPrincipal en Spring Security 3.2.)


Entonces puedes usar en tu controlador

 public ModelAndView someRequestHandler(Principal principal) { User activeUser = (User) ((Authentication) principal).getPrincipal(); ... } 

Eso está bien si lo necesitas una vez. Pero si lo necesita varias veces es feo porque contamina su controlador con detalles de infraestructura, que normalmente debería estar oculto por el marco.

Entonces, lo que realmente puede querer es tener un controlador como este:

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

Por lo tanto, solo necesita implementar un WebArgumentResolver . Tiene un método

 Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception 

Eso obtiene la solicitud web (segundo parámetro) y debe devolver al User si se siente responsable del argumento del método (el primer parámetro).

Desde Spring 3.1 hay un nuevo concepto llamado HandlerMethodArgumentResolver . Si usa Spring 3.1+, entonces debe usarlo. (Se describe en la siguiente sección de esta respuesta))

 public class CurrentUserWebArgumentResolver implements WebArgumentResolver{ Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) { if(methodParameter is for type User && methodParameter is annotated with @ActiveUser) { Principal principal = webRequest.getUserPrincipal(); return (User) ((Authentication) principal).getPrincipal(); } else { return WebArgumentResolver.UNRESOLVED; } } } 

Debe definir la Anotación personalizada: puede omitirla si cada instancia de Usuario siempre debe tomarse del contexto de seguridad, pero nunca es un objeto de comando.

 @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ActiveUser {} 

En la configuración solo necesita agregar esto:

      

@Ver: aprende a personalizar los argumentos del método Spring MVC @Controller

Cabe señalar que si está utilizando Spring 3.1, recomiendan HandlerMethodArgumentResolver sobre WebArgumentResolver. – ver el comentario de Jay


Lo mismo con HandlerMethodArgumentResolver para Spring 3.1+

 public class CurrentUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.getParameterAnnotation(ActiveUser.class) != null && methodParameter.getParameterType().equals(User.class); } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { if (this.supportsParameter(methodParameter)) { Principal principal = webRequest.getUserPrincipal(); return (User) ((Authentication) principal).getPrincipal(); } else { return WebArgumentResolver.UNRESOLVED; } } } 

En la configuración, necesitas agregar esto

      

@See aprovechando la interfaz Spring MVC 3.1 HandlerMethodArgumentResolver


Solución Spring-Security 3.2

Spring Security 3.2 (no confundas con Spring 3.2) tiene una solución de comstackción propia: @AuthenticationPrincipal ( org.springframework.security.web.bind.annotation.AuthenticationPrincipal ). Esto está muy bien descrito en la respuesta de Lukas Schmelzeisen

Solo está escribiendo

 ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) { ... } 

Para que esto funcione, debe registrar AuthenticationPrincipalArgumentResolver ( org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver ): ya sea “activando” @EnableWebMvcSecurity o registrando este bean dentro de mvc:argument-resolvers , de la misma manera que describí con la solución de Spring 3.1 arriba.

@Ver referencia de Spring Security 3.2, Capítulo 11.2. @AuthenticationPrincipal


Solución Spring-Security 4.0

Funciona como la solución Spring 3.2, pero en Spring 4.0 el @AuthenticationPrincipal y AuthenticationPrincipalArgumentResolver se “movieron” a otro paquete:

  • org.springframework.security.core.annotation.AuthenticationPrincipal
  • org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver

(Pero las viejas clases en sus paquetes viejos todavía existen, ¡así que no los mezcles!)

Solo está escribiendo

 import org.springframework.security.core.annotation.AuthenticationPrincipal; ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) { ... } 

Para que esto funcione, debe registrar ( org.springframework.security.web.method.annotation. ) AuthenticationPrincipalArgumentResolver : ya sea “activando” @EnableWebMvcSecurity o registrando este bean dentro de mvc:argument-resolvers , de la misma forma en que lo describí con la solución de spring 3.1 de arriba.

      

@Ver Spring Security 5.0 Referencia, Capítulo 39.3 @AuthenticationPrincipal

Mientras que Ralphs Answer proporciona una solución elegante, con Spring Security 3.2 ya no necesita implementar su propio ArgumentResolver .

Si tiene un UserDetails implementación de CustomUser , puede hacer esto:

 @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) { // .. find messages for this User and return them... } 

Consulte la documentación de Spring Security: @AuthenticationPrincipal

Spring Security está diseñado para funcionar con otras estructuras que no sean de Spring, por lo tanto, no está estrechamente integrado con Spring MVC. Spring Security devuelve el objeto Authentication del método HttpServletRequest.getUserPrincipal() de manera predeterminada, así que eso es lo que obtienes como principal. Puede obtener su objeto UserDetails directamente a partir de esto mediante el uso de

 UserDetails ud = ((Authentication)principal).getPrincipal() 

Tenga en cuenta también que los tipos de objeto pueden variar según el mecanismo de autenticación utilizado (es posible que no obtenga un UsernamePasswordAuthenticationToken , por ejemplo) y que la Authentication no contenga estrictamente un UserDetails . Puede ser una cadena o cualquier otro tipo.

Si no desea llamar directamente a SecurityContextHolder , el enfoque más elegante (que seguiría) es inyectar su propia interfaz de acceso de contexto de seguridad personalizada, que se personaliza para que coincida con sus necesidades y tipos de objetos de usuario. Cree una interfaz, con los métodos relevantes, por ejemplo:

 interface MySecurityAccessor { MyUserDetails getCurrentUser(); // Other methods } 

A continuación, puede implementar esto accediendo a SecurityContextHolder en su implementación estándar, desacoplando por completo su código de Spring Security. Luego, inyecte esto en los controladores que necesitan acceso a información de seguridad o información sobre el usuario actual.

El otro beneficio principal es que es fácil realizar implementaciones simples con datos fijos para las pruebas, sin tener que preocuparse por poblar locales de subprocesos, etc.

Implemente la interfaz HandlerInterceptor y luego UserDetails en cada solicitud que tenga un modelo, de la siguiente manera:

 @Component public class UserInterceptor implements HandlerInterceptor { ....other methods not shown.... public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { if(modelAndView != null){ modelAndView.addObject("user", (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()); } } 

Comenzando con Spring Security versión 3.2, la funcionalidad personalizada que ha sido implementada por algunas de las respuestas anteriores, existe de manera @AuthenticationPrincipal anotación @AuthenticationPrincipal respaldada por AuthenticationPrincipalArgumentResolver .

Un ejemplo simple de su uso es:

 @Controller public class MyController { @RequestMapping("/user/current/show") public String show(@AuthenticationPrincipal CustomUser customUser) { // do something with CustomUser return "view"; } } 

CustomUser debe poder asignarse desde authentication.getPrincipal()

Aquí están los Javadocs correspondientes de AuthenticationPrincipal y AuthenticationPrincipalArgumentResolver

 @Controller public abstract class AbstractController { @ModelAttribute("loggedUser") public User getLoggedUser() { return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } } 

Y si necesita usuario autorizado en plantillas (por ejemplo, JSP) use

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

Juntos con

   org.springframework.security spring-security-taglibs ${spring-security.version}