Spring Auth Server:X509证书触发后强制校验client_id异常问题
问题场景
我们把项目升级到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

