我正在使用Spring Boot 1.3,Spring 4.2和Spring Security 4.0。我正在使用MockMvc运行集成测试,例如:
mockMvc = webAppContextSetup(webApplicationContext).build();
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
在我的测试中,我正在模拟这样的用户登录:
CurrentUser principal = new CurrentUser(user);
Authentication auth =
new UsernamePasswordAuthenticationToken(principal, "dummypassword",
principal.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
这适用于我用@PreAuthorize
注释的方法,例如从测试中调用这样的方法时:
@PreAuthorize("@permissionsService.canDoThisThing(principal)")
public void mySecuredMethod()
原则,我的CurrentUser对象,在PermissionsService#canDoThisThing
中是非空的。
我有一个用@ControllerAdvice注释的类,它将当前登录的用户添加到模型中,以便可以在每个视图中访问它:
@ControllerAdvice
public class CurrentUserControllerAdvice {
@ModelAttribute("currentUser")
public CurrentUser getCurrentUser(Authentication authentication) {
if (authentication == null) {
return null;
}
return (CurrentUser) authentication.getPrincipal();
}
}
这在运行应用程序时工作正常(但这是我的问题) - 运行我的测试时,传入上面的getCurrentUser方法的身份验证参数始终为null。这意味着我的视图模板中对currentUser属性的任何引用都会导致错误,因此这些测试失败。
我知道我可以通过检索这样的原理来解决这个问题:
authentication = SecurityContextHolder.getContext().getAuthentication();
但我宁愿不改变我的主要代码,以便测试工作。
答案 0 :(得分:2)
将Spring Security与SecurityContextHolder
一起使用时,设置MockMvc
不起作用。原因是Spring Security的SecurityContextPersistenceFilter
尝试从SecurityContext
解析HttpServletRequest
。默认情况下,这是通过在名为HttpSessionSecurityContextRepository
的{{1}}属性处进行检索,使用HttpSession
完成的。属性名称由常量SPRING_SECURITY_CONTEXT
定义。无论HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY
中找到SecurityContext
的是什么,都会在HttpSession
上设置,这会覆盖您之前设置的值。
涉及最少量更改的修补方法是在SecurityContextHolder
中设置SecurityContext
。您可以使用以下内容执行此操作:
HttpSession
关键是要确保将名为MvcResult result = mockMvc.perform(get("/").sessionAttr(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext))
.andExpect(status().isOk())
.etc;
的{{1}}属性设置为HttpSession
。在我们的示例中,我们利用常量SPRING_SECURITY_CONTEXT
来定义属性名称。
Spring Security 4.0已正式添加test support。到目前为止,这是使用Spring Security测试应用程序的最简单,最灵活的方法。
由于您使用的是Spring Boot,因此确保您具有此依赖关系的最简单方法是在Maven pom中包含spring-security-test。 Spring Boot管理版本,因此无需指定版本。
SecurityContext
当然,Gradle包含非常相似。
为了integrate with MockMvc,您必须执行参考中列出的步骤。
第一步是确保您使用HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY
。这应该不足为奇,因为这是使用Spring应用程序进行测试时的标准步骤。
next step是为了确保您使用<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
构建@RunWith(SpringJUnit4ClassRunner.class)
实例。例如:
MockMvc
现在您可以轻松地与特定用户一起运行。使用MockMvc有两种方法可以实现这一点。
第一个选项是using a RequestPostProcessor。对于您的示例,您可以执行以下操作:
SecurityMockMvcConfigurers.springSecurity()
您还可以使用注释来指定用户。由于您使用自定义用户对象(即CurrentUser),因此您可能会考虑使用@WithUserDetails或@WithSecurityContext。
@WithUserDetails如果将import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MyTests {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // sets up Spring Security with MockMvc
.build();
}
...
公开为bean,并且用户被查找(即它必须存在),那么它是有意义的。 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
// use this if CustomUser (principal) implements UserDetails
mvc
.perform(get("/").with(user(principal)))
...
// otherwise use this
mvc
.perform(get("/").with(authentication(auth)))
...
的示例可能如下所示:
UserDetailsService
另一种方法是使用@WithSecurityContext。如果您不想要求用户实际存在(这是WithUserDetails所必需的),这是有意义的。我不会详细说明这一点,因为它有详细记录,如果没有关于对象模型的更多细节,我无法提供具体的例子。