5

我正在使用 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 方法的身份验证参数始终为空。这意味着在我的视图模板中对 currentUser 属性的任何引用都会导致错误,因此这些测试会失败。

我知道我可以通过检索这样的原则来解决这个问题:

authentication = SecurityContextHolder.getContext().getAuthentication();

但我宁愿不更改我的主要代码,以便测试工作。

4

1 回答 1

4

SecurityContextHolderSpring Security 与MockMvc. 原因是 Spring SecuritySecurityContextPersistenceFilter试图SecurityContextHttpServletRequest. 默认情况下,这是HttpSessionSecurityContextRepository通过检索HttpSession名为 的属性来完成的SPRING_SECURITY_CONTEXT。属性名称由常量定义HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEYSecurityContext在 中找到的任何内容HttpSession都将被设置在SecurityContextHolder覆盖您之前设置的值上。

手动解决问题

涉及最少更改的修复是SecurityContextHttpSession. 您可以使用以下方法执行此操作:

MvcResult result = mockMvc.perform(get("/").sessionAttr(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext))
            .andExpect(status().isOk())
            .etc;

关键是确保您将HttpSession名为的属性设置SPRING_SECURITY_CONTEXTSecurityContext. 在我们的示例中,我们利用常量HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY来定义属性名称。

春季安全测试

Spring Security 4.0 正式增加了测试支持。这是迄今为止使用 Spring Security 测试应用程序的最简单、最灵活的方法。

添加弹簧安全测试

由于您使用的是 Spring Boot,因此确保您拥有这种依赖关系的最简单方法是在您的 Maven pom.xml 中包含 spring-security-test。Spring Boot 管理版本,因此无需指定版本。

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

自然,Gradle 包含将非常相似。

设置 MockMvc

为了与 MockMvc 集成,您必须执行参考中列出的一些步骤。

第一步是确保您使用@RunWith(SpringJUnit4ClassRunner.class). 这应该不足为奇,因为这是使用 Spring 应用程序进行测试时的标准步骤。

下一步MockMvc是确保您使用SecurityMockMvcConfigurers.springSecurity(). 例如:

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();
    }

...

以用户身份运行

现在您可以轻松地与特定用户一起运行。使用 MockMvc 有两种方法可以完成此任务。

使用 RequestPostProcessor

第一个选项是使用 RequestPostProcessor。对于您的示例,您可以执行以下操作:

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)))
    ...

使用注解

您还可以使用注释来指定用户。由于您使用自定义用户对象(即 CurrentUser),您可能会考虑使用@WithUserDetails@WithSecurityContext

@WithUserDetails如果您将 公开UserDetailsService为 bean 并且您对正在查找的用户没问题(即它必须存在),那么它是有意义的。一个示例@WithUserDetails可能如下所示:

@Test
@WithUserDetails("usernameThatIsFoundByUserDetailsService")
public void run() throws Exception {
    MvcResult result = mockMvc.perform(get("/"))
        .andExpect(status().isOk())
        .etc;
}

另一种方法是使用@WithSecurityContext。如果您不想要求用户实际存在(这是 WithUserDetails 所必需的),这是有道理的。我不会详细说明这一点,因为它有据可查,并且没有关于您的对象模型的更多详细信息,我无法提供具体的示例。

于 2015-12-11T14:56:45.650 回答