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 SecurityContextHolder
nel mio controller, voglio iniettare qualcosa che usa SecurityContextHolder
sotto 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 SecurityContextHolder
stato ereditato da acegi, ma comunque. Il fatto è che sono così vicini - se solo SecurityContextHolder
avessi un getter per ottenere l' SecurityContextHolderStrategy
istanza 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 SecurityContextHolderStrategy
utilizzato da SecurityContextHolder
è, per impostazione predefinita, un'istanza di ThreadLocalSecurityContextHolderStrategy
, che memorizza SecurityContext
s in a ThreadLocal
. Pertanto, non è necessariamente una buona idea iniettare SecurityContext
direttamente un bean al momento dell'inizializzazione: potrebbe essere necessario recuperarlo ThreadLocal
ogni volta, in un ambiente multi-thread, quindi viene recuperato quello corretto.