测试时无法Mock Spring JWT资源服务器,请求排查问题
我用自建的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

