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

如何在Spring Boot单元测试中Mock JWT认证?

解决Spring Boot REST Controller测试中Mock JWT认证的问题

我明白你遇到的困扰——集成Auth0 JWT认证后,原本正常的Controller单元测试因为缺少有效令牌返回401,没法顺利验证业务逻辑。下面给你几种实用的解决方案,完全适配你的测试场景:

方法1:手动Mock JWT认证上下文(最直接)

Spring Security允许我们直接操控SecurityContext,注入Mock的JWT认证信息,不需要真的生成有效令牌。

修改测试方法示例

@Test
public void shouldGetAllRoundsByUserId() throws Exception {
    // 1. 构建符合要求的Mock JWT对象
    Jwt mockJwt = Jwt.withTokenValue("mock-token")
            .header("alg", "HS256")
            .claim("sub", STUB_USER_ID) // 这里设置业务需要的用户ID
            .claim("aud", "your-audience-value") // 替换成你配置文件里的audience值
            .issuer("your-issuer-value") // 替换成你配置文件里的issuer值
            .build();

    // 2. 创建JWT认证令牌并注入SecurityContext
    JwtAuthenticationToken authentication = new JwtAuthenticationToken(mockJwt);
    SecurityContextHolder.getContext().setAuthentication(authentication);

    // 原有测试逻辑保持不变
    given(userRoundService.getAllRoundsByUserId(STUB_USER_ID)).willReturn(
            Collections.singletonList(round));
    RequestBuilder requestBuilder = Requests.getAllRoundsByUserId(STUB_USER_ID);
    MockHttpServletResponse response = mockMvc.perform(requestBuilder)
            .andReturn()
            .getResponse();

    assertNotNull(response);
    assertEquals(HttpStatus.OK.value(), response.getStatus());
}

这种方式的好处是完全模拟了真实的认证流程,同时不需要修改请求头,适合需要验证认证信息(比如从JWT获取用户ID)的业务场景。

方法2:Mock JwtDecoder Bean(适合集成测试)

如果你用了@SpringBootTest做集成测试,可以直接替换容器中的JwtDecoder,让它总是返回有效JWT,这样Spring Security的认证环节会直接通过。

修改测试类示例

@AutoConfigureMockMvc
public class UserRoundsControllerTest extends AbstractUnitTests {
    private static String STUB_USER_ID = "user3";
    private static String STUB_ROUND_ID = "7e3b270222252b2dadd547fb";

    @Autowired
    private MockMvc mockMvc;
    @MockBean // 替换容器中的JwtDecoder
    private JwtDecoder jwtDecoder;

    private Round round;
    private ObjectId objectId;

    @BeforeEach
    public void setUp() {
        initMocks(this);
        round = Mocks.roundOne();
        objectId = Mocks.objectId();

        // 配置Mock的JwtDecoder,任意令牌都返回有效JWT
        Jwt mockJwt = Jwt.withTokenValue("mock-token")
                .header("alg", "HS256")
                .claim("sub", STUB_USER_ID)
                .claim("aud", "your-audience-value")
                .issuer("your-issuer-value")
                .build();
        given(jwtDecoder.decode(anyString())).willReturn(mockJwt);
    }

    @Test
    public void shouldGetAllRoundsByUserId() throws Exception {
        given(userRoundService.getAllRoundsByUserId(STUB_USER_ID)).willReturn(
                Collections.singletonList(round));
        // 请求里添加任意Bearer令牌即可
        RequestBuilder requestBuilder = Requests.getAllRoundsByUserId(STUB_USER_ID)
                .header("Authorization", "Bearer any-random-token");
        MockHttpServletResponse response = mockMvc.perform(requestBuilder)
                .andReturn()
                .getResponse();

        assertNotNull(response);
        assertEquals(HttpStatus.OK.value(), response.getStatus());
    }
}

这种方式不需要手动操作SecurityContext,更贴近真实请求流程,适合需要验证完整请求链路的集成测试。

方法3:测试环境禁用安全配置(快速但谨慎使用)

如果你只是想快速验证Controller的业务逻辑,完全不关心认证环节,可以创建一个测试专用的安全配置:

测试专用SecurityConfig

@Profile("test")
@EnableWebSecurity
public class TestSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
                .anyRequest().permitAll(); // 允许所有请求跳过认证
    }

    // 复用原有CORS配置
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

然后在你的抽象测试类上添加激活测试Profile的注解:

@ExtendWith(SpringExtension.class)
@SpringBootTest(
        classes = PokerStatApplication.class,
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
@ActiveProfiles("test") // 激活测试专用配置
public abstract class AbstractUnitTests {
    // mock objects etc
}

注意:这种方法会完全跳过认证逻辑,如果你的业务代码依赖JWT中的用户信息(比如从SecurityContext获取用户ID),这种方式就不适用了,仅适合纯业务逻辑的快速验证。

方案推荐

  • 如果需要验证认证信息的业务逻辑,优先选方法1;
  • 如果是完整链路的集成测试,优先选方法2;
  • 方法3仅作为临时快速测试的备选,不推荐长期使用。

你还可以把Mock JWT的逻辑封装成工具类,减少重复代码,比如:

public class JwtMockHelper {
    public static Jwt createMockJwt(String userId, String audience, String issuer) {
        return Jwt.withTokenValue("mock-token")
                .header("alg", "HS256")
                .claim("sub", userId)
                .claim("aud", audience)
                .issuer(issuer)
                .build();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 14:27:59