You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何@WithMockUser配置正确,RestAssured测试仍失败?

为何带有@WithMockUser的RestAssured测试无法通过方法级安全验证?

我来帮你拆解这个问题——你遇到的核心痛点其实是线程隔离导致的SecurityContext无法传递,咱们一步步说清楚:

先还原你的场景

你有一个带方法级安全的控制器:

@RestController
public class DummyController {
    @GetMapping("/")
    @PreAuthorize("hasRole('TEST')")
    public String test() {
        return "hello";
    }
}

宽松的安全配置(允许所有请求,但开启方法级安全):

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll();
    }
}

还有失败的RestAssured测试:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
public class DummyControllerITest {
    @LocalServerPort
    private int port;

    @Test
    @WithMockUser(roles = "TEST")
    public void name() throws Exception {
        RestAssured.given()
            .port(port)
            .when()
            .get("/")
            .then()
            .statusCode(HttpStatus.OK.value());
    }
}

为什么测试会失败?

你调试发现的现象已经很关键了:测试线程有SecurityContext,但请求处理线程没有。

原因很简单:

  • @WithMockUser的作用是把模拟用户信息绑定到当前测试线程的SecurityContext里,它只在这个线程内有效。
  • 而RestAssured是通过发送真实的HTTP请求到Spring Boot的嵌入式服务器,服务器会用另一个独立的线程来处理这个请求。两个线程的SecurityContext是完全隔离的,所以处理请求的线程根本拿不到你在测试线程里设置的模拟用户。

换句话说,@WithMockUser是给同线程的代码用的(比如直接调用Service层方法测试),但跨线程的HTTP请求(比如RestAssured、Postman这类外部调用)根本吃不到这个注解的红利。

怎么解决?

核心思路是:让身份信息通过HTTP请求本身传递给服务器,让服务器在处理请求的线程中重新构建合法的SecurityContext。最直接的方案是用Spring Security的HTTP认证机制,比如Basic认证,下面给你完整的实现:

1. 修改SecurityConfig,开启Basic认证并配置测试用户

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll()
            .and()
            .httpBasic(); // 开启Basic认证支持
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 配置内存测试用户,角色为TEST
        auth.inMemoryAuthentication()
            .withUser("test-user")
            .password("{noop}test-pass") // {noop}表示不加密,仅测试用
            .roles("TEST");
    }
}

2. 修改RestAssured测试,添加Basic认证信息

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
public class DummyControllerITest {
    @LocalServerPort
    private int port;

    @Test
    public void testWithValidRole() throws Exception {
        RestAssured.given()
            .port(port)
            .auth().basic("test-user", "test-pass") // 携带Basic认证信息
            .when()
            .get("/")
            .then()
            .statusCode(HttpStatus.OK.value());
    }
}

这样修改后,测试就能正常通过了——因为RestAssured会把用户名密码通过Authorization请求头发送给服务器,Spring Security的BasicAuthenticationFilter会解析这个请求头,验证用户身份,然后把正确的SecurityContext设置到请求处理线程中,@PreAuthorize("hasRole('TEST')")就能正确识别用户角色了。

其他可选方案

如果你不想用Basic认证,也可以:

  • 用JWT令牌:生成测试用的JWT,在请求头里携带Authorization: Bearer <token>
  • 自定义RestAssured过滤器:把测试线程的SecurityContext序列化后放到请求头,再写一个服务器端过滤器解析并设置到请求线程(这个相对复杂,一般测试用Basic足够)

另外,如果你愿意替换RestAssured为Spring官方的TestRestTemplate,它可以和@WithMockUser配合使用——因为TestRestTemplate是Spring内部的客户端,会自动把测试线程的SecurityContext传递到请求处理线程,不过这属于替换工具的方案了。

内容的提问来源于stack exchange,提问作者user1978011

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:50:56