SpringBootTest集成测试中禁用安全校验及安全测试实现方案(基于RestTestClient.bindToServer())
SpringBootTest集成测试中禁用安全校验及安全测试实现方案(基于RestTestClient.bindToServer())
我来帮你分析问题并给出针对性的解决方案,先解释下你之前尝试的方法为什么没生效:
@WithMockUser/@WithAnonymousUser无效:这类注解是模拟测试线程的认证上下文,但RestTestClient是发送真实HTTP请求到服务器,服务器的处理线程和测试线程完全独立,上下文无法共享,所以自然不生效。- 排除
SecurityAutoConfiguration没生效:Spring Boot的安全自动配置包含多个关联类(比如SecurityFilterAutoConfiguration),如果你的项目还有自定义安全配置类,排除单个AutoConfig类无法完全覆盖原有规则。 @AutoConfigureMockMvc(addFilters = false)无效:这个注解是针对MockMvc模拟测试的,和你用的真实服务器绑定的RestTestClient完全不相关,所以不起作用。
一、如何禁用安全校验(让测试通过)
最可靠的方式是在测试中添加测试专用的安全配置,直接覆盖原有安全规则,不会影响主应用的配置。
方案:添加测试专用SecurityFilterChain
修改你的测试类,新增一个静态的测试配置类,定义允许所有请求的安全过滤链:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class DemoControllerIntegrationTest { @LocalServerPort private int port; private RestTestClient restClient; @BeforeEach void beforeEachTest() { restClient = RestTestClient.bindToServer() .baseUrl("http://localhost:" + port) .build(); } // 测试专用安全配置:允许所有请求,禁用不必要的CSRF @TestConfiguration static class TestSecurityConfig { @Bean public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) .csrf(csrf -> csrf.disable()); // 测试场景下通常不需要CSRF保护 return http.build(); } } @Test void should_return_uppercase_request_uri_unsecured_home() { String uri = "/unsecured/home"; String expected = "/UNSECURED/HOME"; restClient.get() .uri(uri) .exchange() .expectStatus() .isOk() .expectBody(String.class) .isEqualTo(expected); } }
这样测试启动时会优先加载这个测试配置,替换原有安全规则,所有请求都会被允许,你的测试就能正常通过了。
二、正确实现安全测试(带认证请求)
因为RestTestClient绑定真实服务器,必须通过HTTP请求传递真实认证信息,以下是几种常见认证场景的实现:
1. Basic 用户名密码认证
可以全局设置默认认证头,或单个请求单独设置:
// 全局设置:所有请求自动带上Basic认证 @BeforeEach void beforeEachTest() { restClient = RestTestClient.bindToServer() .baseUrl("http://localhost:" + port) .defaultHeaders(headers -> { // 替换为你的测试用用户名密码 headers.setBasicAuth("test-user", "test-123"); }) .build(); } // 单个请求单独设置 @Test void testSecuredEndpoint() { restClient.get() .uri("/secured/info") .headers(headers -> headers.setBasicAuth("test-user", "test-123")) .exchange() .expectStatus().isOk(); }
2. JWT Token 认证
先获取测试用JWT,再在请求头中携带:
@BeforeEach void beforeEachTest() { // 1. 先调用登录接口获取JWT Token String jwtToken = restClient.post() .uri("/auth/login") .body("{\"username\":\"test-user\",\"password\":\"test-123\"}") .exchange() .expectBody(String.class) .returnResult() .getResponseBody(); // 2. 初始化RestClient时默认携带Token restClient = RestTestClient.bindToServer() .baseUrl("http://localhost:" + port) .defaultHeaders(headers -> { headers.setBearerAuth(jwtToken); }) .build(); }
3. 表单登录(会话认证)
先完成登录获取会话Cookie,后续请求携带该Cookie:
@Test void testSecuredPageWithFormLogin() { // 1. 发送登录请求,获取会话Cookie ClientResponse loginResponse = restClient.post() .uri("/login") .body("{\"username\":\"test-user\",\"password\":\"test-123\"}") .exchange() .expectStatus().is3xxRedirection() .returnResult(Void.class) .getResponse(); // 提取会话Cookie String sessionCookie = loginResponse.headers().firstHeader(HttpHeaders.SET_COOKIE); // 2. 携带Cookie访问受保护接口 restClient.get() .uri("/secured/dashboard") .headers(headers -> headers.add(HttpHeaders.COOKIE, sessionCookie)) .exchange() .expectStatus().isOk(); }
备注:内容来源于stack exchange,提问作者Saravana Kumar M
相关产品推荐
相关产品推荐

