Spring Boot 3.2.1 OAuth2资源服务器与FusionAuth租户配置问题
这并非Spring Boot的Bug,而是自动配置逻辑的特性导致的:
当Spring Boot检测到配置了spring.security.oauth2.resourceserver.jwt.jwk-set-uri(包括自动从OIDC发现地址中获取到该值的场景),会触发ReactiveOAuth2ResourceServerJwkConfiguration中对应的jwtDecoder Bean创建逻辑。这个逻辑会直接使用配置的issuer-uri值作为JWT校验的issuer,而不会从OIDC配置响应中提取实际的issuer值,这就导致了配置的issuer-uri(租户OIDC发现地址)与JWT中的iss声明("anyissuer")不匹配,从而抛出校验错误。
你可以通过自定义ReactiveJwtDecoder来实现从OIDC配置中动态获取issuer并完成校验,提供两种方案供选择:
方案1:完全自定义ReactiveJwtDecoder Bean
1. 排除自动配置类
首先排除Spring Boot默认的JWT解码器自动配置类,避免干扰:
@SpringBootApplication(exclude = {ReactiveOAuth2ResourceServerJwkConfiguration.class}) public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
2. 自定义解码器配置
创建配置类,手动从OIDC发现地址拉取配置,提取正确的issuer和JWKS地址,构建解码器并配置校验规则:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.web.reactive.function.client.WebClient; import java.util.Map; @Configuration public class CustomOAuth2ResourceServerConfig { private final String oidcDiscoveryUri = "http://localhost:9011/.well-known/openid-configuration/018d642c-e707-7349-95c7-ead15f625746"; private final String targetAudience = "018d642c-a7e8-75b1-96af-1ea04c3b0faa"; @Bean public ReactiveJwtDecoder reactiveJwtDecoder() { // 拉取OIDC配置 Map<String, Object> oidcConfig = WebClient.create() .get() .uri(oidcDiscoveryUri) .retrieve() .bodyToMono(Map.class) .block(); if (oidcConfig == null) { throw new IllegalStateException("无法从地址 " + oidcDiscoveryUri + " 获取OIDC配置"); } String jwkSetUri = (String) oidcConfig.get("jwks_uri"); String actualIssuer = (String) oidcConfig.get("issuer"); // 构建JWT解码器 NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build(); // 配置组合校验器:issuer校验 + audience校验 OAuth2TokenValidator<Jwt> issuerValidator = JwtValidators.createDefaultWithIssuer(actualIssuer); OAuth2TokenValidator<Jwt> audienceValidator = new JwtAudienceValidator(targetAudience); OAuth2TokenValidator<Jwt> combinedValidator = OAuth2TokenValidator.composite(issuerValidator, audienceValidator); decoder.setJwtValidator(combinedValidator); return decoder; } // 自定义Audience校验器 static class JwtAudienceValidator implements OAuth2TokenValidator<Jwt> { private final String audience; public JwtAudienceValidator(String audience) { this.audience = audience; } @Override public OAuth2TokenValidatorResult validate(Jwt jwt) { return jwt.getAudience().contains(audience) ? OAuth2TokenValidatorResult.success() : OAuth2TokenValidatorResult.failure( new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "Audience不匹配", null) ); } } }
方案2:基于自动配置扩展校验器
保留原有的application.yml配置,通过自定义ReactiveJwtDecoderCustomizer替换默认的issuer校验逻辑,无需排除自动配置类:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.web.reactive.function.client.WebClient; import java.util.Map; @Configuration public class JwtDecoderCustomConfig { private final String oidcDiscoveryUri = "http://localhost:9011/.well-known/openid-configuration/018d642c-e707-7349-95c7-ead15f625746"; private final String targetAudience = "018d642c-a7e8-75b1-96af-1ea04c3b0faa"; @Bean public NimbusReactiveJwtDecoder.ReactiveJwtDecoderCustomizer jwtDecoderCustomizer() { return decoder -> { // 拉取OIDC配置获取实际issuer Map<String, Object> oidcConfig = WebClient.create() .get() .uri(oidcDiscoveryUri) .retrieve() .bodyToMono(Map.class) .block(); if (oidcConfig != null) { String actualIssuer = (String) oidcConfig.get("issuer"); // 构建新的校验器 OAuth2TokenValidator<Jwt> issuerValidator = JwtValidators.createDefaultWithIssuer(actualIssuer); OAuth2TokenValidator<Jwt> audienceValidator = jwt -> jwt.getAudience().contains(targetAudience) ? OAuth2TokenValidatorResult.success() : OAuth2TokenValidatorResult.failure( new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "Audience不匹配", null) ); decoder.setJwtValidator(OAuth2TokenValidator.composite(issuerValidator, audienceValidator)); } }; } }
两种方案的核心逻辑一致:从OIDC配置响应中提取实际的issuer值作为校验标准,而非直接使用配置的issuer-uri。方案1灵活性更高,适合需要深度定制解码器的场景;方案2更简洁,基于自动配置扩展,侵入性更低。
内容的提问来源于stack exchange,提问作者Asenjo

