这个问题被回答后,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();
}
}
而且,由于接口是解耦的点,因此单元测试很简单。在这个例子中,我使用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
实例(它是一个接口)的getter方法就可以进行注入。事实上,我甚至提出了一个 Jira 问题。
最后一件事 - 我刚刚大幅修改了之前的答案。如果你感兴趣,可以查看历史记录,但正如一位同事指出的那样,我的之前的答案在多线程环境下无法工作。由SecurityContextHolder
使用的底层SecurityContextHolderStrategy
默认情况下是ThreadLocalSecurityContextHolderStrategy
的一个实例,它将SecurityContext
存储在ThreadLocal
中。因此,在初始化时直接将SecurityContext
注入到bean中可能不是一个好主意 - 在多线程环境中,每次需要从ThreadLocal
中检索,以便正确地检索到它。