如何在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
相关产品推荐
相关产品推荐

