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

Spring Auth Server:X509证书触发后强制校验client_id异常问题

解决Spring Authorization Server在Cloud Foundry HTTPS环境下X509证书干扰客户端认证的问题

问题场景

我们把项目升级到Spring Boot 3.3和Spring Authorization Server 1.3.0,采用OAuth2授权码流。客户端默认使用client_secret_basic认证,授权服务端也对应配置了该认证方式:

客户端YAML配置:

spring:
  security:
    oauth2:
      client:
        registration:
          local:
            authorization-grant-type: authorization_code
            client-id: client
            client-secret: secret
            redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
            scope: "openid,profile"
        provider:
          local:
            issuer-uri: http://localhost:8081
            user-name-attribute: sub

授权服务端Kotlin配置:

@Bean
fun registeredClientRepository(): RegisteredClientRepository {
    val registeredClient =
        RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("local")
            .clientSecret("{noop}secret")
            .clientAuthenticationMethod(CLIENT_SECRET_BASIC)
            .authorizationGrantType(AUTHORIZATION_CODE)
            .redirectUri("http://localhost:8080/login/oauth2/code/aad")
            .scope(OPENID)
            .scope(PROFILE)
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
            .build()
    return InMemoryRegisteredClientRepository(registeredClient)
}

本地HTTP环境运行完全正常,但部署到Cloud Foundry的HTTPS环境后出现问题:请求会自动携带X509证书,触发X509ClientCertificateAuthenticationConverter,导致两个关键错误:

  • 使用client_secret_basic时,OAuth2AuthorizationCodeGrantRequestEntityConverter不会添加client_id参数,但X509ClientCertificateAuthenticationConverter要求必须存在该参数,直接抛出INVALID_REQUEST异常。
  • 改用client_secret_post后,X509ClientCertificateAuthenticationConverter会将认证方法强制改为tls_client_auth,完全无视我们配置的认证方式。

目前找到临时方案:添加过滤器移除jakarta.servlet.request.X509Certificate属性以阻止转换器生效,但不想采用这种hack方式,希望让配置的客户端认证方法正常生效,不受平台自动添加的无关证书干扰。

可行解决方案

1. 调整客户端认证转换器的执行顺序

Spring Security的客户端认证转换器按顺序匹配,X509ClientCertificateAuthenticationConverter默认优先级较高。我们可以自定义转换器链,把需要的client_secret_basic或client_secret_post转换器放在前面,确保先匹配到配置的认证方式,跳过X509转换器的处理。

示例Kotlin代码:

@Bean
fun clientAuthenticationConverter(): OAuth2ClientAuthenticationConverter {
    val delegatingConverter = DelegatingOAuth2ClientAuthenticationConverter()
    // 优先添加client_secret_basic转换器,确保优先匹配
    delegatingConverter.addConverter(ClientSecretBasicAuthenticationConverter())
    // 如果使用client_secret_post,添加对应转换器
    delegatingConverter.addConverter(ClientSecretPostAuthenticationConverter())
    // 最后添加X509转换器
    delegatingConverter.addConverter(X509ClientCertificateAuthenticationConverter())
    return delegatingConverter
}

@Bean
fun clientAuthenticationManager(
    registeredClientRepository: RegisteredClientRepository,
    clientAuthenticationConverter: OAuth2ClientAuthenticationConverter
): OAuth2ClientAuthenticationManager {
    val manager = OAuth2ClientAuthenticationManager(registeredClientRepository)
    manager.setClientAuthenticationConverter(clientAuthenticationConverter)
    return manager
}

2. 直接禁用X509客户端认证转换器

如果Cloud Foundry添加的X509证书并非用于客户端认证,可以直接在授权服务端配置中移除X509ClientCertificateAuthenticationConverter,彻底避免它干扰正常认证流程。

示例Kotlin代码:

@Bean
fun authorizationServerSettings(): AuthorizationServerSettings {
    return AuthorizationServerSettings.builder()
        .clientAuthenticationConverters { converters ->
            // 移除X509认证转换器
            converters.removeIf { it is X509ClientCertificateAuthenticationConverter }
            converters
        }
        .build()
}

3. 给client_secret_basic请求强制添加client_id参数

针对client_secret_basic场景下缺少client_id的问题,可以自定义OAuth2AuthorizationCodeGrantRequestEntityConverter,强制将client_id加入请求参数,满足X509转换器的参数要求(若不想调整转换器顺序)。

示例Kotlin代码:

@Bean
fun authorizationCodeGrantRequestEntityConverter(): OAuth2AuthorizationCodeGrantRequestEntityConverter {
    val converter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
    converter.addParametersConverter { parameters, clientRegistration ->
        // 强制添加client_id参数
        if (!parameters.containsKey(OAuth2ParameterNames.CLIENT_ID)) {
            parameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.clientId)
        }
        parameters
    }
    return converter
}

验证

将调整后的代码部署到Cloud Foundry后,测试OAuth2授权码流:

  • 客户端使用client_secret_basic时,请求会正常携带client_id,不会触发X509转换器的异常。
  • 使用client_secret_post时,认证方法不会被强制改为tls_client_auth,按配置方式正常完成认证。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 02:05:59