基于声明的角色分配多租户资源服务器测试遇403错误
问题解决:Spring Boot资源服务器集成测试返回403
问题根源
集成测试中使用@WithMockJwtAuth生成的Mock JWT不会经过你自定义的JwtAuthConverter,导致外部发行者对应的角色映射逻辑未执行。最终认证对象缺少ROLE_tester权限,触发403 insufficient_scope错误。而Postman测试正常是因为真实JWT会完整走一遍资源服务器的认证流程,包括转换器的角色映射。
解决方案
方案一:测试注解中手动指定权限
直接在@WithMockJwtAuth中添加所需权限,快速解决测试问题:
@Test @WithMockJwtAuth( claims = OpenIdClaims( name = "example-name", iss = "http://known.issuer/realms/test", ), authorities = ["ROLE_tester"] ) fun `should succeed`() { mockMvc.get("/api/test/123abc") .andExpect { status { isOk() } } }
方案二:自定义测试注解自动映射角色
创建自定义注解,结合测试上下文工厂,自动根据发行者URI匹配角色,减少测试代码重复:
// 自定义测试注解 @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @WithSecurityContext(factory = WithMockIssuerAuthSecurityContextFactory::class) annotation class WithMockIssuerAuth( val iss: String, val name: String = "example-name" ) // 上下文工厂实现 class WithMockIssuerAuthSecurityContextFactory( private val externalIssuers: ExternalIssuers ) : WithSecurityContextFactory<WithMockIssuerAuth> { override fun createSecurityContext(annotation: WithMockIssuerAuth): SecurityContext { val issuer = externalIssuers.issuers.find { it.uri == annotation.iss } ?: throw IllegalArgumentException("未配置的发行者:${annotation.iss}") val jwt = Jwt.withTokenValue("mock-token") .header("alg", "none") .claim("iss", annotation.iss) .claim("preferred_username", annotation.name) .build() val authorities = listOf(SimpleGrantedAuthority("ROLE_${issuer.role}")) val auth = JwtAuthenticationToken(jwt, authorities, annotation.name) val context = SecurityContextHolder.createEmptyContext() context.authentication = auth return context } }
使用自定义注解编写测试:
@Test @WithMockIssuerAuth(iss = "http://known.issuer/realms/test") fun `should succeed`() { mockMvc.get("/api/test/123abc") .andExpect { status { isOk() } } }
方案三:手动触发转换器处理Mock JWT
在测试中直接调用自定义转换器,生成正确的认证对象:
@SpringBootTest @AutoConfigureMockMvc class YourTestClass { @Autowired private lateinit var mockMvc: MockMvc @Autowired private lateinit var jwtAuthConverter: JwtAuthConverter @Test fun `should succeed`() { // 构造Mock JWT val mockJwt = Jwt.withTokenValue("mock-token") .header("alg", "none") .claim("iss", "http://known.issuer/realms/test") .claim("preferred_username", "example-name") .build() // 用自定义转换器生成认证对象 val auth = jwtAuthConverter.convert(mockJwt) // 携带认证对象发起请求 mockMvc.get("/api/test/123abc") .with(SecurityMockMvcRequestPostProcessors.authentication(auth)) .andExpect { status { isOk() } } } }
内容的提问来源于stack exchange,提问作者naptoon
相关产品推荐
相关产品推荐

