Многое изменилось в весеннем мире с тех пор, как на этот вопрос был дан ответ. Spring упростил получение текущего пользователя в контроллере. Для других бинов 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
хранится в ThreadLocal
. Следовательно, не обязательно хорошая идея внедрять SecurityContext
непосредственно в bean-компонент во время инициализации - его может потребоваться извлекать из ThreadLocal
каждый раз в многопоточной среде, так что получается правильный .