Molte cose sono cambiate nel mondo primaverile da quando è stata data una risposta a questa domanda. La primavera ha semplificato il coinvolgimento dell'utente corrente in un controller. Per altri bean, Spring ha adottato i suggerimenti dell'autore e semplificato l'iniezione di "SecurityContextHolder". Maggiori dettagli sono nei commenti.
Questa è la soluzione con cui ho finito. Invece di utilizzare SecurityContextHoldernel mio controller, voglio iniettare qualcosa che usa SecurityContextHoldersotto il cofano ma estrae quella classe singleton dal mio codice. Non ho trovato alcun modo per farlo se non rotolare la mia interfaccia, in questo modo:
public interface SecurityContextFacade {
SecurityContext getContext();
void setContext(SecurityContext securityContext);
}
Ora, il mio controller (o qualunque POJO) sarebbe simile a questo:
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, poiché l'interfaccia è un punto di disaccoppiamento, i test unitari sono semplici. In questo esempio 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();
}
}
L'implementazione predefinita dell'interfaccia è simile alla seguente:
public class SecurityContextHolderFacade implements SecurityContextFacade {
public SecurityContext getContext() {
return SecurityContextHolder.getContext();
}
public void setContext(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
}
}
E, infine, la configurazione Spring di produzione si presenta così:
<bean id="myController" class="com.foo.FooController">
...
<constructor-arg index="1">
<bean class="com.foo.SecurityContextHolderFacade">
</constructor-arg>
</bean>
Sembra più che un po 'sciocco che Spring, un contenitore di iniezione di dipendenza di tutte le cose, non abbia fornito un modo per iniettare qualcosa di simile. Capisco sia SecurityContextHolderstato ereditato da acegi, ma comunque. Il fatto è che sono così vicini - se solo SecurityContextHolderavessi un getter per ottenere l' SecurityContextHolderStrategyistanza sottostante (che è un'interfaccia), potresti iniettarla. In effetti, ho persino aperto un problema di Jira in tal senso.
Un'ultima cosa: ho appena cambiato sostanzialmente la risposta che avevo qui prima. Controlla la cronologia se sei curioso ma, come mi ha sottolineato un collega, la mia risposta precedente non avrebbe funzionato in un ambiente multi-thread. Il sottostante SecurityContextHolderStrategyutilizzato da SecurityContextHolderè, per impostazione predefinita, un'istanza di ThreadLocalSecurityContextHolderStrategy, che memorizza SecurityContexts in a ThreadLocal. Pertanto, non è necessariamente una buona idea iniettare SecurityContextdirettamente un bean al momento dell'inizializzazione: potrebbe essere necessario recuperarlo ThreadLocalogni volta, in un ambiente multi-thread, quindi viene recuperato quello corretto.