Muita coisa mudou no mundo da primavera desde que essa pergunta foi respondida. O Spring simplificou a obtenção do usuário atual em um controlador. Para outros beans, o Spring adotou as sugestões do autor e simplificou a injeção do 'SecurityContextHolder'. Mais detalhes estão nos comentários.
Essa é a solução que eu acabei usando. Em vez de usar SecurityContextHolder
no meu controlador, quero injetar algo que seja usado SecurityContextHolder
sob o capô, mas que abstraia a classe do tipo código do meu código. Não encontrei outra maneira de fazer isso além de rolar minha própria interface, assim:
public interface SecurityContextFacade {
SecurityContext getContext();
void setContext(SecurityContext securityContext);
}
Agora, meu controlador (ou qualquer outro POJO) ficaria assim:
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
}
}
E, devido à interface ser um ponto de dissociação, o teste de unidade é simples. Neste exemplo eu uso o 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();
}
}
A implementação padrão da interface é assim:
public class SecurityContextHolderFacade implements SecurityContextFacade {
public SecurityContext getContext() {
return SecurityContextHolder.getContext();
}
public void setContext(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
}
}
E, finalmente, a configuração do Spring de produção é assim:
<bean id="myController" class="com.foo.FooController">
...
<constructor-arg index="1">
<bean class="com.foo.SecurityContextHolderFacade">
</constructor-arg>
</bean>
Parece um pouco tolo que o Spring, um recipiente de injeção de dependência de todas as coisas, não tenha fornecido uma maneira de injetar algo semelhante. Eu entendo que SecurityContextHolder
foi herdado do acegi, mas ainda assim. O problema é que eles estão muito próximos - se apenas SecurityContextHolder
houvesse um getter para obter a SecurityContextHolderStrategy
instância subjacente (que é uma interface), você poderia injetar isso. Na verdade, eu até abri um problema com o Jira nesse sentido.
Uma última coisa - eu mudei substancialmente a resposta que tinha aqui antes. Verifique o histórico se você estiver curioso, mas, como um colega de trabalho apontou para mim, minha resposta anterior não funcionaria em um ambiente multithread. O subjacente SecurityContextHolderStrategy
usado por SecurityContextHolder
é, por padrão, uma instância de ThreadLocalSecurityContextHolderStrategy
, que armazena SecurityContext
s em a ThreadLocal
. Portanto, não é necessariamente uma boa idéia injetar o SecurityContext
diretamente em um bean no momento da inicialização - pode ser necessário recuperá-lo de ThreadLocal
cada vez, em um ambiente multithread, para que o correto seja recuperado.