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

测试时无法Mock Spring JWT资源服务器,请求排查问题

问题:Mock Keycloak JWT时授权失败返回403 Access Denied

我用自建的Keycloak作为授权服务器,测试时希望Mock该实例而非访问真实服务。已定义如下SecurityFilterChain:

@Bean
public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception{
    http.csrf(AbstractHttpConfigurer::disable);
    http.authorizeHttpRequests(requests -> requests
                                .anyRequest().hasRole("USER")
                                );
    http.oauth2ResourceServer(oauth2 -> {
        oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter));
        oauth2.authenticationEntryPoint(unauthorizedHandler);
        }
        );
//    http.addFilterAfter()
    http.exceptionHandling(exception -> {
        exception.accessDeniedHandler(accessDeniedHandler);
    });
    return http.build();
}

同时编写了Mock JWT授权的处理器:

@Bean
public SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor jwtRequestPostProcessor() {
    SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor data = jwt().jwt(builder -> {
        builder.issuer("http://auth.localhost/realms/CompetitionManager");
        builder.claim("azp", "competition-manager-web");
        builder.claim("preferred_username", "testUser");
        builder.claim("email", "test@example.org");
        builder.claim("resource_access", Map.of("competition-manager-web", Map.of("roles", List.of("USER"))));
    });
    return data;
}

测试代码如下:

@SpringJUnitConfig(classes = {JwtTestConfig.class})
public class ContestControllerTest {
    
    /* Autowiring JPA repositories & interfaces */
    
    
    @Autowired
    private SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor defaultJwt;

    @Test
    @Order(2)
    public void getListOfContests_then200() throws Exception {
        mockMvc.perform(get("/contests/list").with(defaultJwt))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$").isArray())
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[*]", containsInAnyOrder("visible test contest 1", "visible test contest 2")));
    }
}

按照文档配置后仍无法通过授权,返回403“Access Denied”。确认Token已正确传递(未返回401),且其他测试中preferred_username可正常访问使用,但无法通过授权过滤。请问我的Token定义是否遗漏了内容?


解决方案

返回403说明用户已成功认证,但缺少访问所需的权限。问题出在JWT中的角色没有被正确转换为Spring Security识别的权限,可以从以下几个方向排查修复:

1. 检查自定义JWT转换器的角色解析逻辑

你配置了jwtAuthenticationConverter,这个转换器负责将JWT声明转换为Spring Security的GrantedAuthority实例。Spring Security的hasRole("USER")实际上校验的是ROLE_USER权限(默认自动添加ROLE_前缀)。

如果你的转换器是从resource_access.competition-manager-web.roles提取角色,需要确保它给角色添加了ROLE_前缀,示例实现如下:

public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {
    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
        Map<String, Object> clientRoles = (Map<String, Object>) resourceAccess.get("competition-manager-web");
        List<String> roles = (List<String>) clientRoles.get("roles");
        
        Collection<GrantedAuthority> authorities = roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList());
                
        return new JwtAuthenticationToken(jwt, authorities);
    }
}

同时要确保测试配置JwtTestConfig中已经加载了这个转换器Bean,否则角色无法被正确解析。

2. 直接在Mock JWT中注入目标权限

如果想简化测试逻辑,不依赖转换器的解析过程,可以直接在Mock处理器中添加Spring Security需要的权限:

@Bean
public SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor jwtRequestPostProcessor() {
    return jwt()
            .jwt(builder -> {
                builder.issuer("http://auth.localhost/realms/CompetitionManager");
                builder.claim("azp", "competition-manager-web");
                builder.claim("preferred_username", "testUser");
                builder.claim("email", "test@example.org");
                builder.claim("resource_access", Map.of("competition-manager-web", Map.of("roles", List.of("USER"))));
            })
            .authorities(new SimpleGrantedAuthority("ROLE_USER")); // 直接添加对应权限
}

这样测试请求会直接携带ROLE_USER权限,无需依赖转换器即可通过hasRole("USER")的校验。

3. 确认测试配置加载了必要组件

测试类使用@SpringJUnitConfig(classes = {JwtTestConfig.class}),需要确保JwtTestConfig中不仅包含Mock处理器,还导入了主配置中的jwtAuthConverter、SecurityFilterChain等相关Bean,否则测试环境的安全配置不完整,会导致权限校验异常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 03:34:57