Beaucoup de choses ont changé dans le monde du printemps depuis la réponse à cette question. Spring a simplifié le placement de l'utilisateur actuel dans un contrôleur. Pour les autres beans, Spring a adopté les suggestions de l'auteur et simplifié l'injection de 'SecurityContextHolder'. Plus de détails sont dans les commentaires.
C'est la solution avec laquelle j'ai fini par aller. Au lieu d'utiliser SecurityContextHolderdans mon contrôleur, je veux injecter quelque chose qui utilise SecurityContextHoldersous le capot mais qui résume cette classe de type singleton de mon code. Je n'ai trouvé aucun autre moyen que de rouler ma propre interface, comme ceci:
public interface SecurityContextFacade {
SecurityContext getContext();
void setContext(SecurityContext securityContext);
}
Maintenant, mon contrôleur (ou n'importe quel POJO) ressemblerait à ceci:
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
}
}
Et, comme l'interface est un point de découplage, le test unitaire est simple. Dans cet exemple, j'utilise 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'implémentation par défaut de l'interface ressemble à ceci:
public class SecurityContextHolderFacade implements SecurityContextFacade {
public SecurityContext getContext() {
return SecurityContextHolder.getContext();
}
public void setContext(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
}
}
Et, enfin, la configuration de production de Spring ressemble à ceci:
<bean id="myController" class="com.foo.FooController">
...
<constructor-arg index="1">
<bean class="com.foo.SecurityContextHolderFacade">
</constructor-arg>
</bean>
Il semble plus qu'un peu stupide que Spring, un conteneur d'injection de dépendance de toutes choses, n'a pas fourni un moyen d'injecter quelque chose de similaire. Je comprends que a SecurityContextHolderété hérité d'Acegi, mais quand même. Le fait est qu'ils sont si proches - si seulement SecurityContextHolderun getter pouvait obtenir l' SecurityContextHolderStrategyinstance sous-jacente (qui est une interface), vous pourriez l'injecter. En fait, j'ai même ouvert un dossier Jira à cet effet.
Une dernière chose - je viens de changer considérablement la réponse que j'avais ici auparavant. Consultez l'historique si vous êtes curieux mais, comme un collègue me l'a fait remarquer, ma réponse précédente ne fonctionnerait pas dans un environnement multi-thread. Le sous-jacent SecurityContextHolderStrategyutilisé par SecurityContextHolderest, par défaut, une instance de ThreadLocalSecurityContextHolderStrategy, qui stocke SecurityContexts dans a ThreadLocal. Par conséquent, ce n'est pas nécessairement une bonne idée d'injecter le SecurityContextdirectement dans un bean au moment de l'initialisation - il peut être nécessaire de le récupérer à ThreadLocalchaque fois, dans un environnement multi-thread, afin que le bon soit récupéré.