在回答了这个问题之后,Spring世界发生了很多变化。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有一个getter来获得底层的SecurityContextHolderStrategy实例(这是一个接口),你就可以注入它。事实上,我甚至为此开了一期Jira杂志。
One last thing - I've just substantially changed the answer I had here before. Check the history if you're curious but, as a coworker pointed out to me, my previous answer would not work in a multi-threaded environment. The underlying SecurityContextHolderStrategy used by SecurityContextHolder is, by default, an instance of ThreadLocalSecurityContextHolderStrategy, which stores SecurityContexts in a ThreadLocal. Therefore, it is not necessarily a good idea to inject the SecurityContext directly into a bean at initialization time - it may need to be retrieved from the ThreadLocal each time, in a multi-threaded environment, so the correct one is retrieved.