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 SecurityContextHolder
dans mon contrôleur, je veux injecter quelque chose qui utilise SecurityContextHolder
sous 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 SecurityContextHolder
un getter pouvait obtenir l' SecurityContextHolderStrategy
instance 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 SecurityContextHolderStrategy
utilisé par SecurityContextHolder
est, par défaut, une instance de ThreadLocalSecurityContextHolderStrategy
, qui stocke SecurityContext
s dans a ThreadLocal
. Par conséquent, ce n'est pas nécessairement une bonne idée d'injecter le SecurityContext
directement dans un bean au moment de l'initialisation - il peut être nécessaire de le récupérer à ThreadLocal
chaque fois, dans un environnement multi-thread, afin que le bon soit récupéré.