1. 简介

在不需要独立集成环境的情况下运行集成测试,是任何软件技术栈中都非常有价值的能力。Spring Boot 与 Spring Security 的无缝集成,使得我们可以轻松地对涉及安全层的组件进行测试。

本文将快速带你了解如何使用 @WebMvcTest@SpringBootTest 来执行带有安全机制的集成测试。

2. 依赖配置

我们首先引入示例所需的依赖项:

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

其中:

此外,我们还引入了 spring-security-test,以便使用 @WithMockUser 注解。

3. Web 安全配置

我们的 Web 安全配置相对简单:

✅ 只有认证用户才能访问 /private/** 路径
❌ 所有用户都可以访问 /public/** 路径

@Configuration
public class WebSecurityConfigurer {

    @Bean
    public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails user = User.withUsername("spring")
            .password(passwordEncoder.encode("secret"))
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/private/**"))
                .hasRole("USER"))
            .authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/public/**"))
                .permitAll())
            .httpBasic(Customizer.withDefaults())
            .build();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

4. 方法级安全配置

除了基于 URL 的安全控制外,我们还可以通过方法级别的安全配置来增强保护:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfigurer 
  extends GlobalMethodSecurityConfiguration {
}

这段配置启用了 Spring Security 的 @PreAuthorize@PostAuthorize 等注解支持。

⚠️ 注意:如果需要更复杂的方法权限控制,可以进一步扩展配置。

更多关于方法级安全的内容,请参考 Spring Security 方法安全详解

5. 使用 @WebMvcTest 测试 Controller

当使用 @WebMvcTest 注解时,Spring Boot 会自动配置好用于测试的安全过滤器链,因此可以直接使用 @WithMockUser

@RunWith(SpringRunner.class)
@WebMvcTest(SecuredController.class)
public class SecuredControllerWebMvcIntegrationTest {

    @Autowired
    private MockMvc mvc;

    // ... other methods

    @WithMockUser(value = "spring")
    @Test
    public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
        mvc.perform(get("/private/hello").contentType(MediaType.APPLICATION_JSON))
          .andExpect(status().isOk());
    }
}

✅ 小贴士:@WebMvcTest 只加载 Web 层,不启动完整上下文,因此速度更快。

6. 使用 @SpringBootTest 测试 Controller

使用 @SpringBootTest 时,我们需要手动配置 MockMvc 的安全过滤器链:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SecuredControllerSpringBootIntegrationTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mvc;

    @Before
    public void setup() {
        mvc = MockMvcBuilders
          .webAppContextSetup(context)
          .apply(springSecurity())
          .build();
    }

    // ... other methods

    @WithMockUser("spring")
    @Test
    public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
        mvc.perform(get("/private/hello").contentType(MediaType.APPLICATION_JSON))
          .andExpect(status().isOk());
    }
}

✅ 推荐做法:使用 springSecurity() 静态方法配置安全过滤器链。

7. 使用 @SpringBootTest 测试受保护的方法

对于方法级别的安全测试,无需额外配置,可直接调用并使用 @WithMockUser

@RunWith(SpringRunner.class)
@SpringBootTest
public class SecuredMethodSpringBootIntegrationTest {

    @Autowired
    private SecuredService service;

    @Test(expected = AuthenticationCredentialsNotFoundException.class)
    public void givenUnauthenticated_whenCallService_thenThrowsException() {
        service.sayHelloSecured();
    }

    @WithMockUser(username="spring")
    @Test
    public void givenAuthenticated_whenCallServiceWithSecured_thenOk() {
        assertThat(service.sayHelloSecured()).isNotBlank();
    }
}

✅ 这种方式适合测试 Service 层的安全逻辑。

8. 使用 @SpringBootTestTestRestTemplate 测试 REST 接口

TestRestTemplate 是测试受保护 REST 接口的一个便捷选择:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SecuredControllerRestTemplateIntegrationTest {

    @Autowired
    private TestRestTemplate template;

    // ... other methods

    @Test
    public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
        ResponseEntity<String> result = template.withBasicAuth("spring", "secret")
          .getForEntity("/private/hello", String.class);
        assertEquals(HttpStatus.OK, result.getStatusCode());
    }
}

TestRestTemplate 提供了灵活的认证方式,适用于端到端测试。

了解更多 TestRestTemplate 的用法,请查看 这篇文章

9. 总结

本文介绍了多种在 Spring Boot 中执行带安全验证的集成测试方式:

  • 使用 @WebMvcTest 快速测试 Controller
  • 使用 @SpringBootTest + MockMvc 做完整上下文测试
  • 直接调用 Service 方法测试方法级安全
  • 利用 TestRestTemplate 发起真实 HTTP 请求

所有代码示例均可在 GitHub 获取。


原始标题:Spring Security for Spring Boot Integration Tests