С тех пор, как на этот вопрос был дан ответ, многое изменилось в мире весны. Spring упростил получение текущего пользователя в контроллере. Для других bean-компонентов Spring принял предложения автора и упростил внедрение SecurityContextHolder. Более подробная информация в комментариях.
Это решение, с которым я в конечном итоге пошел. Вместо того, чтобы использовать SecurityContextHolder
в моем контроллере, я хочу внедрить что-то, что использует SecurityContextHolder
скрытно, но абстрагирует этот синглтоноподобный класс от моего кода. Я не нашел другого способа сделать это, кроме как развернуть свой собственный интерфейс, вот так:
public interface SecurityContextFacade {
SecurityContext getContext();
void setContext(SecurityContext securityContext);
}
Теперь мой контроллер (или любой другой POJO) будет выглядеть так:
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
}
}
И, поскольку интерфейс является точкой развязки, модульное тестирование является простым. В этом примере я использую 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();
}
}
Реализация интерфейса по умолчанию выглядит следующим образом:
public class SecurityContextHolderFacade implements SecurityContextFacade {
public SecurityContext getContext() {
return SecurityContextHolder.getContext();
}
public void setContext(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
}
}
И, наконец, рабочий конфигурационный Spring выглядит так:
<bean id="myController" class="com.foo.FooController">
...
<constructor-arg index="1">
<bean class="com.foo.SecurityContextHolderFacade">
</constructor-arg>
</bean>
Более чем глупым кажется то, что Spring, контейнер для инъекций зависимостей, не предоставил способ внедрить нечто подобное. Я понимаю, что SecurityContextHolder
унаследовал от acegi, но все же. Дело в том, что они так близки - если бы у SecurityContextHolder
них был только геттер для получения базового SecurityContextHolderStrategy
экземпляра (который является интерфейсом), вы могли бы внедрить его. На самом деле, я даже открыл проблему Jira на этот счет.
И последнее: я просто существенно изменил ответ, который у меня был здесь раньше. Проверьте историю, если вам интересно, но, как указал мне коллега, мой предыдущий ответ не будет работать в многопоточной среде. По умолчанию SecurityContextHolderStrategy
используется базовый SecurityContextHolder
экземпляр ThreadLocalSecurityContextHolderStrategy
, который хранит SecurityContext
s в ThreadLocal
. Следовательно, не обязательно хорошая идея вставлять SecurityContext
объект непосредственно в bean-компонент во время инициализации - его может потребоваться извлекать ThreadLocal
каждый раз, в многопоточной среде, так что получается правильный.