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 beans, 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 se usa SecurityContextHolder
debajo del capó pero abstrae esa clase de tipo 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 cualquier 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 uso 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í. El hecho es que están tan cerca: si solo SecurityContextHolder
tuviera un getter para obtener la SecurityContextHolderStrategy
instancia subyacente (que es una interfaz), podría inyectar eso. De hecho, incluso abrí un problema de Jira en ese sentido.
Una última cosa: acabo de cambiar sustancialmente la respuesta que tenía aquí antes. Verifique el historial 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. El subyacente SecurityContextHolderStrategy
utilizado por SecurityContextHolder
es, por defecto, una instancia de ThreadLocalSecurityContextHolderStrategy
, que almacena SecurityContext
s en a 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, de modo que se recupere el correcto.