Cómo configurar manualmente un usuario autenticado en Spring Security / SpringMVC

Después de que un nuevo usuario envía un formulario de “Nueva cuenta”, quiero iniciar sesión manualmente en ese usuario para que no tenga que iniciar sesión en la página siguiente.

La página de inicio de sesión normal que atraviesa el interceptor de seguridad de spring funciona bien.

En el controlador new-account-form estoy creando un UsernamePasswordAuthenticationToken y configurándolo en SecurityContext manualmente:

SecurityContextHolder.getContext().setAuthentication(authentication); 

En esa misma página, verifico más tarde que el usuario ha iniciado sesión con:

 SecurityContextHolder.getContext().getAuthentication().getAuthorities(); 

Esto devuelve las autorizaciones que configuré antes en la autenticación. Todo está bien.

Pero cuando se llama este mismo código en la página siguiente que cargo, el token de autenticación es UserAnonymous.

No estoy seguro de por qué no guardó la autenticación que configuré en la solicitud anterior. ¿Alguna idea?

  • ¿Podría tener que ver con que las ID de sesión no estén configuradas correctamente?
  • ¿Hay algo que posiblemente sobrescriba mi autenticación de alguna manera?
  • Tal vez solo necesito otro paso para guardar la autenticación?
  • ¿O hay algo que tengo que hacer para declarar la autenticación en toda la sesión en lugar de una única solicitud de alguna manera?

Solo estoy buscando algunos pensamientos que puedan ayudarme a ver qué está pasando aquí.

Tuve el mismo problema que hace un tiempo. No recuerdo los detalles, pero el siguiente código hizo que las cosas funcionen para mí. Este código se usa dentro de un flujo Spring Webflow, de ahí las clases RequestContext y ExternalContext. Pero la parte que es más relevante para usted es el método doAutoLogin.

 public String registerUser(UserRegistrationFormBean userRegistrationFormBean, RequestContext requestContext, ExternalContext externalContext) { try { Locale userLocale = requestContext.getExternalContext().getLocale(); this.userService.createNewUser(userRegistrationFormBean, userLocale, Constants.SYSTEM_USER_ID); String emailAddress = userRegistrationFormBean.getChooseEmailAddressFormBean().getEmailAddress(); String password = userRegistrationFormBean.getChoosePasswordFormBean().getPassword(); doAutoLogin(emailAddress, password, (HttpServletRequest) externalContext.getNativeRequest()); return "success"; } catch (EmailAddressNotUniqueException e) { MessageResolver messageResolvable = new MessageBuilder().error() .source(UserRegistrationFormBean.PROPERTYNAME_EMAIL_ADDRESS) .code("userRegistration.emailAddress.not.unique") .build(); requestContext.getMessageContext().addMessage(messageResolvable); return "error"; } } private void doAutoLogin(String username, String password, HttpServletRequest request) { try { // Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); token.setDetails(new WebAuthenticationDetails(request)); Authentication authentication = this.authenticationProvider.authenticate(token); logger.debug("Logging in with [{}]", authentication.getPrincipal()); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (Exception e) { SecurityContextHolder.getContext().setAuthentication(null); logger.error("Failure in autoLogin", e); } } 

No pude encontrar ninguna otra solución completa, así que pensé en publicar la mía. Esto puede ser un poco un hack, pero resolvió el problema con el problema anterior:

 public void login(HttpServletRequest request, String userName, String password) { UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password); // Authenticate the user Authentication authentication = authenticationManager.authenticate(authRequest); SecurityContext securityContext = SecurityContextHolder.getContext(); securityContext.setAuthentication(authentication); // Create a new session and add the security context. HttpSession session = request.getSession(true); session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext); } 

Finalmente descubrió la raíz del problema.

Cuando creo el contexto de seguridad manualmente, no se crea ningún objeto de sesión. Solo cuando la solicitud finaliza el procesamiento, el mecanismo de Spring Security se da cuenta de que el objeto de sesión es nulo (cuando intenta almacenar el contexto de seguridad en la sesión una vez que se ha procesado la solicitud).

Al final de la solicitud, Spring Security crea un nuevo objeto de sesión e ID de sesión. Sin embargo, esta nueva ID de sesión nunca llega al navegador porque ocurre al final de la solicitud, después de que se haya realizado la respuesta al navegador. Esto hace que la nueva ID de sesión (y, por lo tanto, el contexto de seguridad que contiene mi usuario conectado manualmente) se pierda cuando la próxima solicitud contenga la ID de sesión anterior.

Active el registro de depuración para obtener una mejor idea de lo que está sucediendo.

Puede saber si las cookies de sesión se configuran mediante un depurador del lado del navegador para ver los encabezados devueltos en las respuestas HTTP. (También hay otras formas).

Una posibilidad es que SpringSecurity establezca cookies de sesión seguras, y su próxima página solicitada tiene una URL “http” en lugar de una URL “https”. (El navegador no enviará una cookie segura para una URL “http”).

La nueva función de filtrado en Servlet 2.4 básicamente alivia la restricción de que los filtros solo pueden operar en el flujo de solicitud antes y después del procesamiento de solicitud real por parte del servidor de aplicaciones. En su lugar, los filtros de Servlet 2.4 ahora pueden interactuar con el despachador de solicitudes en cada punto de envío. Esto significa que cuando un recurso Web reenvía una solicitud a otro recurso (por ejemplo, un servlet reenviando la solicitud a una página JSP en la misma aplicación), un filtro puede estar funcionando antes de que el recurso objective maneje la solicitud. También significa que si un recurso web incluye la salida o función de otros recursos web (por ejemplo, una página JSP que incluye el resultado de varias otras páginas JSP), los filtros Servlet 2.4 pueden funcionar antes y después de cada uno de los recursos incluidos. .

Para activar esa función necesita:

web.xml

  springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy   springSecurityFilterChain /* REQUEST FORWARD  

RegistrationController

 return "forward:/login?j_username=" + registrationModel.getUserEmail() + "&j_password=" + registrationModel.getPassword(); 

Estaba tratando de probar una aplicación extjs y después de establecer con éxito un TestAuthenticationToken esto dejó de funcionar repentinamente sin una causa obvia.

No pude obtener las respuestas anteriores para que funcione, así que mi solución fue saltear este poco de spring en el entorno de prueba. Introduje una costura alrededor de la spring como esta:

 public class SpringUserAccessor implements UserAccessor { @Override public User getUser() { SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); return (User) authentication.getPrincipal(); } } 

El usuario es un tipo personalizado aquí.

Lo estoy envolviendo en una clase que solo tiene una opción para que el código de prueba cambie de fuente.

 public class CurrentUserAccessor { private static UserAccessor _accessor; public CurrentUserAccessor() { _accessor = new SpringUserAccessor(); } public User getUser() { return _accessor.getUser(); } public static void UseTestingAccessor(User user) { _accessor = new TestUserAccessor(user); } } 

La versión de prueba se ve así:

 public class TestUserAccessor implements UserAccessor { private static User _user; public TestUserAccessor(User user) { _user = user; } @Override public User getUser() { return _user; } } 

En el código de llamada, todavía estoy usando un usuario adecuado cargado desde la base de datos:

  User user = (User) _userService.loadUserByUsername(username); CurrentUserAccessor.UseTestingAccessor(user); 

Obviamente, esto no será adecuado si realmente necesita utilizar la seguridad, pero estoy ejecutando una configuración sin seguridad para la implementación de prueba. Pensé que alguien más podría encontrarse en una situación similar. Este es un patrón que he usado para burlar dependencias estáticas antes. La otra alternativa es que puede mantener la estática de la clase contenedora, pero prefiero esta ya que las dependencias del código son más explícitas ya que debe pasar CurrentUserAccessor a las clases donde se requiere.