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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 00:10:01