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

基于声明的角色分配多租户资源服务器测试遇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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 12:30:20