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

Spring Boot OAuth2 Client对接Azure AD B2C时Issuer URI不匹配问题

解决Spring Boot OAuth2 Client对接Azure AD B2C的Issuer不匹配问题

问题说明

我搭建了Spring Boot OAuth2 Client对接Azure AD B2C,配置如下:

spring:
  security:
    oauth2:
      client:
        registration:
          azuread:
            authorization-grant-type: authorization_code
            client-id: 'client id'
            client-secret: 'secret'
            provider: azuread
            scope: openid
            client-name: demo
            user-flows:
                sign-up-or-sign-in: <policy name>
        provider:
          azuread:
            issuer-uri: https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com/<policy-name>/v2.0/.well-known/openid-configuration

启动应用时触发报错:

"The Issuer provided in the configuration metadata did not match the requested issuer"

排查后发现,Spring根据配置的issuer-uri拉取元数据后,校验Issuer出现不一致:

预期值:
https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com//v2.0/

实际结果:
https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com/v2.0/

要求:不使用Spring Boot Azure AD B2C Starter,仅通过原生OAuth2 Client解决该问题。


解决方案

方法1:直接调整配置文件,跳过元数据自动发现

手动指定所有OAuth2端点,同时把issuer-uri设置为实际返回的正确值,避免自动发现时的路径解析问题:

spring:
  security:
    oauth2:
      client:
        registration:
          azuread:
            authorization-grant-type: authorization_code
            client-id: 'client id'
            client-secret: 'secret'
            provider: azuread
            scope: openid
            client-name: demo
            # 手动配置授权、令牌、JWKS端点
            authorization-uri: https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com/<policy-name>/oauth2/v2.0/authorize
            token-uri: https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com/<policy-name>/oauth2/v2.0/token
            jwk-set-uri: https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com/<policy-name>/discovery/v2.0/keys
        provider:
          azuread:
            # 设置为实际返回的Issuer值
            issuer-uri: https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com/v2.0/

这种方式直接绕开元数据自动发现的路径解析问题,确保Issuer校验完全匹配。

方法2:自定义Issuer校验逻辑

如果需要保留元数据自动发现,可以自定义JWT校验器,忽略Issuer中的斜杠差异:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoders;
import org.springframework.security.oauth2.jwt.JwtValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;

@Configuration
public class OAuth2IssuerConfig {

    @Bean
    public JwtDecoder jwtDecoder(ClientRegistrationRepository clientRegistrationRepository) {
        var registration = clientRegistrationRepository.findByRegistrationId("azuread");
        NimbusJwtDecoder decoder = JwtDecoders.fromIssuerLocation(registration.getIssuerUri());
        
        // 自定义校验器,处理斜杠差异
        JwtValidator validator = jwt -> {
            String expectedIssuer = registration.getIssuerUri();
            String actualIssuer = jwt.getIssuer().toString();
            // 去掉所有重复斜杠后对比
            if (!expectedIssuer.replaceAll("//", "/").equals(actualIssuer.replaceAll("//", "/"))) {
                throw new IllegalArgumentException("Issuer mismatch");
            }
            return jwt;
        };
        
        decoder.setJwtValidator(validator);
        return decoder;
    }
}

这个配置会覆盖默认的Issuer校验逻辑,允许预期值和实际值之间的斜杠差异。

方法3:自定义元数据解析逻辑

通过修改元数据解析过程,调整返回的Issuer值,使其和配置中的预期值匹配:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.core.endpoint.OAuth2TokenResponse;

import java.util.Map;

@Configuration
public class AzureB2CMetadataConfig {

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        ClientRegistration registration = ClientRegistration.withRegistrationId("azuread")
                .clientId("your-client-id")
                .clientSecret("your-client-secret")
                .authorizationGrantType(org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE)
                .scope("openid")
                .authorizationUri("https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com/<policy-name>/oauth2/v2.0/authorize")
                .tokenUri("https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com/<policy-name>/oauth2/v2.0/token")
                .jwkSetUri("https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com/<policy-name>/discovery/v2.0/keys")
                .issuerUri("https://{clientId}.b2clogin.com/{clientId}.onmicrosoft.com//v2.0/") // 配置预期的带双斜杠的Issuer
                .clientName("demo")
                .build();
        return new InMemoryClientRegistrationRepository(registration);
    }

    @Bean
    public DefaultAuthorizationCodeTokenResponseClient tokenResponseClient() {
        DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
        // 修改响应解析逻辑,把实际Issuer改成带双斜杠的预期值
        client.setTokenResponseConverter(response -> {
            Map<String, Object> body = response.getBody();
            String actualIssuer = (String) body.get("iss");
            // 替换成预期的带双斜杠的格式
            body.put("iss", actualIssuer.replace("/v2.0/", "//v2.0/"));
            return OAuth2TokenResponse.withResponseParameters(body)
                    .tokenType(body.get("token_type").toString())
                    .accessToken(body.get("access_token").toString())
                    .expiresIn(Long.parseLong(body.get("expires_in").toString()))
                    .idToken(body.get("id_token").toString())
                    .build();
        });
        return client;
    }
}

内容的提问来源于stack exchange,提问作者user1593966

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 02:05:18