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

如何在spring-boot-starter-oauth2-client+RestClient中传递grant_type?

解决Spring Boot OAuth2 Client传递grant_type参数的问题

首先明确:Spring Security OAuth2 Client中配置的authorization-grant-type: client_credentials,最终会在请求令牌时自动发送grant_type=client_credentials参数,只是默认是作为表单参数放在请求体中,而你的curl命令是将该参数放在URL查询字符串里。第三方返回400,大概率是参数位置不符合要求,而非Spring不支持传递grant_type参数。

以下是两种针对性解决方案:

1. 修正基础配置(适用于第三方接受表单参数的情况)

先检查并修正配置,确保Spring正确生成符合要求的令牌请求:

  • scope配置格式有误,应该用列表形式声明多个权限,而非用+连接的字符串:
spring:
  application:
    name: client-application
  security:
    oauth2:
      client:
        registration:
          my-client:
            provider: the-provider
            client-id: theusername
            client-secret: thepassword
            authorization-grant-type: client_credentials
            scope:
              - resolve
              - download
        provider:
          the-provider:
            token-uri: https://thirdpartyservice.com/token
logging:
  level:
    root: DEBUG

修正后,Spring会自动在令牌请求的表单参数中发送scope=resolve+download,和你的curl行为一致。

如果修正后仍报错,说明第三方要求grant_type必须在URL查询参数中,需要使用下面的自定义方案。

2. 自定义令牌请求,将参数放到URL查询字符串

通过自定义OAuth2AccessTokenResponseClient,修改令牌请求的构建逻辑,把grant_type、scope等参数放到URL的查询参数中:

2.1 自定义ClientCredentialsTokenResponseClient

import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsTokenResponseClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.stream.Collectors;

public class CustomClientCredentialsTokenResponseClient extends OAuth2ClientCredentialsTokenResponseClient {

    @Override
    protected URI expandTokenUri(OAuth2ClientCredentialsGrantRequest grantRequest) {
        ClientRegistration registration = grantRequest.getClientRegistration();
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(registration.getProviderDetails().getTokenUri());

        // 添加grant_type到查询参数
        uriBuilder.queryParam(OAuth2ParameterNames.GRANT_TYPE, registration.getAuthorizationGrantType().getValue());
        // 添加scope到查询参数
        String scope = registration.getScopes().stream().collect(Collectors.joining("+"));
        uriBuilder.queryParam(OAuth2ParameterNames.SCOPE, scope);

        return uriBuilder.build().toUri();
    }

    // 移除请求体中的表单参数(因为已放到URL里)
    @Override
    protected org.springframework.http.RequestEntity<?> createRequest(OAuth2ClientCredentialsGrantRequest grantRequest) {
        org.springframework.http.RequestEntity<?> originalRequest = super.createRequest(grantRequest);
        return org.springframework.http.RequestEntity.post(originalRequest.getUrl())
                .headers(originalRequest.getHeaders())
                .build();
    }
}

2.2 配置自定义的TokenResponseClient

在配置类中注册这个自定义Bean,替换默认实现:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsTokenResponseClient;

@Configuration
public class OAuth2ClientConfig {

    @Bean
    public OAuth2ClientCredentialsTokenResponseClient customClientCredentialsTokenResponseClient() {
        return new CustomClientCredentialsTokenResponseClient();
    }
}

2.3 配置OAuth2AuthorizedClientManager

确保RestClient使用的OAuth2AuthorizedClientManager会调用自定义客户端:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.http.OAuth2ClientHttpRequestInterceptor;
import org.springframework.web.client.RestClient;

@Configuration
public class RestClientConfig {

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository,
            OAuth2ClientCredentialsTokenResponseClient tokenResponseClient) {

        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials(c -> c.accessTokenResponseClient(tokenResponseClient))
                .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    @Bean
    public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
        return RestClient.builder()
                .requestInterceptor(interceptor)
                .build();
    }
}

这样配置后,Spring发送的令牌请求会和你的curl命令完全一致:将grant_type和scope作为URL查询参数,同时用Basic Auth传递client-id和client-secret,符合第三方服务的要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 01:17:04