java - ¿SecurityContext#setAuthentication garantiza la visibilidad?
multithreading spring-security (3)
Java tiene un conjunto de reglas estrictas y bien definidas que dictan la consistencia de la memoria. En resumen, algunas cosas están garantizadas para suceder, antes que otras. Puedes leer sobre esto here . Una forma de lograr que suceda antes es a través de la palabra clave volatile
, y otra es a través de la synchronized
.
Al igual que con cualquier documentación, si Spring no aclara explícitamente lo que garantiza para usted, entonces no debe asumir que garantiza lo que desea.
En resumen, debe ajustar el código que cambia o usa la variable en el bloque synchronized
. Esto garantizará que el valor de una variable no cambie hasta que termine de usarla. Es posible que Spring actualice las variables, pero no garantiza la visibilidad entre los subprocesos ni la sincronización porque solo su código sabe cuándo y cómo se usa esa variable.
Tenga en cuenta que debe usar synchronized (x)
donde x es algo global, constante, fijo y compartido entre todos los subprocesos (es decir, MyClass.class), NO la variable en sí.
Uso la seguridad de primavera en mi proyecto.
Tengo función para cambiar el inicio de sesión. Para lograr este objetivo utilizo el siguiente código.
Authentication authentication = ...
SecurityContextHolder.getContext().setAuthentication(authentication);
Pero ahora estoy reescribiendo este código en detalles y veo que el campo de autenticación no es volatile
lo que la visibilidad no está garantizada:
public class SecurityContextImpl implements SecurityContext {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// ~ Instance fields
// ================================================================================================
private Authentication authentication;
¿Debo envolver mi código con mi propia sincronización para lograr visibilidad?
PD
He leído https://stackoverflow.com/a/30781541/2674303
En una aplicación que recibe solicitudes simultáneas en una sola sesión, la misma instancia de SecurityContext se compartirá entre subprocesos. Aunque se está utilizando un ThreadLocal, es la misma instancia que se recupera de HttpSession para cada subproceso. Esto tiene implicaciones si desea cambiar temporalmente el contexto bajo el cual se está ejecutando un hilo. Si solo usa SecurityContextHolder.getContext () y llama a setAuthentication (anAuthentication) en el objeto de contexto devuelto, el objeto de autenticación cambiará en todos los subprocesos concurrentes que comparten la misma instancia de SecurityContext. Puede personalizar el comportamiento de SecurityContextPersistenceFilter para crear un SecurityContext completamente nuevo para cada solicitud, evitando que los cambios en un hilo afecten a otro. Alternativamente, puede crear una nueva instancia justo en el punto donde cambia temporalmente el contexto. El método SecurityContextHolder.createEmptyContext () siempre devuelve una nueva instancia de contexto.
pero no entiendo cómo hace la primavera la visibilidad de las garantías. Se acaba de escribir que cada hilo dentro de la sesión verá cambios. Pero no hay respuesta, ¿qué tan rápido? Y más importante - no se explica el mecanismo de visibilidad.
Si no está creando nuevos subprocesos o utilizando @Async
, su enfoque de configuración de su nueva autenticación en SecurityContext es correcto. Si crea nuevos subprocesos, entonces se dividen en dos casos de uso: los nuevos subprocesos se crean antes o después de cambiar la autenticación. Si sus subprocesos se crean después de configurar el nuevo objeto de autenticación, entonces tendrá que crear una fábrica de subprocesos que tenga en cuenta la autenticación y copiará el objeto de autenticación a los subprocesos que cree. Si sus subprocesos se crearon antes, entonces eche un vistazo a AuthenticationSuccessEvent
y AuthenticationEventPublisher
, son la forma oficial de Spring de señalar eventos de autenticación, podría haber algún mecanismo útil para propagar los cambios de autenticación en todos los subprocesos que actualmente usa ese usuario.
En una de mis aplicaciones anteriores tuve que implementar una funcionalidad en la que los usuarios administradores pudieran hacerse pasar por usuarios regulares para ayudarlos a solucionar problemas con la aplicación, SecurityContextHolder.getContext().setAuthentication(authentication);
Usted describió se usó y nunca tuvimos problemas con Spring, lo que confunde a qué usuario debería realizar las acciones. La aplicación se ejecutó en un clúster de múltiples nodos con un caché de sesión común compartido por todos los nodos.
Sus dudas están justificadas, la visibilidad no está garantizada . ThreadLocal no es seguro para subprocesos, cuando todas las entradas de ThreadLocalMap almacenan el mismo objeto.
La parte de la documentación a la que se hace referencia, que almacena el SecurityContext entre solicitudes , le advierte sobre ese hecho y propone posibles soluciones para cambiar el contexto de una manera que evite el efecto en otros subprocesos.
Un ejemplo de dicha solución es el mecanismo RunAs , que cambia el contexto durante la fase de devolución de llamada segura del objeto.
Pero, según entiendo su pregunta, debe cambiar el inicio de sesión del usuario (es decir, el nombre de usuario) "sobre la marcha". Si estoy en lo cierto, entonces el problema es que cuando configuras una Authentication
modificada, otro hilo podría leer el valor anterior. Para evitar esta condición de carrera , debe hacer un inicio de sesión de escritura happens-before cada inicio de sesión secuencial.
Authentication
interfaz de Authentication
tiene el método getPrincipal()
, que devuelve un Object
, que es una instancia de UserDetails
(en la mayoría de los casos). Este objeto se usa generalmente para obtener un nombre de usuario para el usuario actual (autenticado).
Por lo tanto, si desea cambiar el inicio de sesión del usuario autenticado "sobre la marcha", puede modificar la propiedad de username
en este objeto UserDetails
.
Una posible forma de hacerlo de manera segura para subprocesos es una implementación personalizada de UserDetails
con la propiedad de volatile String username
( User
implementación predeterminada del User
tiene un nombre de usuario inmutable).
También debe crear y conectar a su configuración una implementación UserDetailsService
, que utilizará sus UserDetails
personalizados.