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

如何将Spring Framework 6 HttpInterface与spring-boot-starter-oauth2-client集成

问题描述

我已经成功使用spring-boot-starter-oauth2-client结合RestClient实现OAuth2令牌获取及受保护资源访问,相关配置与代码如下:

原RestClient实现代码

配置类

@Configuration
public class RestClientConfig {

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

}

控制器

@RestController
public class LessonsController {

    private final RestClient restClient;

    public LessonsController(RestClient restClient) {
        this.restClient = restClient;
    }

    @GetMapping("/lessons")
    public String fetchLessons() {
        return restClient.get()
                .uri("https://someserver.om/someprotectedresource")
                .attributes(clientRegistrationId("my-client"))
                .retrieve()
                .body(String.class);
    }
}

配置文件

spring:
  application:
    name: client-application
  security:
    oauth2:
      client:
        registration:
          my-client:
            provider: my-provider
            client-id: ididid
            client-secret: secretsecret
            authorization-grant-type: client_credentials
            scope: download
        provider:
          my-provider:
            token-uri: https://provider.com/token

上述方案运行正常,已确认能从令牌提供商获取令牌并访问资源服务器的受保护资源。

现尝试改用Spring Framework 6的HttpInterface实现相同功能,但无法获取令牌,怀疑是缺少RestClient中类似.attributes(clientRegistrationId("my-client"))的配置,相关代码如下:

现有HttpInterface代码

配置类

@Configuration
public class UserClientConfig {

    private final RestClient restClient;

    public UserClientConfig(OAuth2AuthorizedClientManager authorizedClientManager, RestClient.Builder restClientBuilder) {
        OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
        this.restClient = restClientBuilder
                .requestInterceptor(interceptor)
                .baseUrl("https://host.com")
                .build();
    }

    @Bean
    public UserClient userClient() {
        RestClientAdapter adapter = RestClientAdapter.create(restClient);
        return HttpServiceProxyFactory.builderFor(adapter)
                .build()
                .createClient(UserClient.class);
    }

}

HttpInterface接口

@HttpExchange(
        url = "/v1",
        accept = MediaType.APPLICATION_JSON_VALUE)
public interface UserClient {

    @GetExchange("/protectedresource/full")
    User getUserById(@RequestParam Map<String, String> params);

}

控制器调用

@GetMapping("/lessons")
public User fetchLessons() {
    return userClient.getUserById(Map.of("foo", "bar"));
}

请问如何将Spring Framework 6 HttpInterface与spring-boot-starter-oauth2-client的令牌机制结合使用?


解决方案

核心是将clientRegistrationId传递给OAuth2拦截器,以下是三种可行方式:

方式一:通过@HttpExchange注解指定固定clientId

在HttpInterface的类或方法级别,利用@HttpExchange的attributes参数直接设置clientRegistrationId,拦截器会自动识别该属性并获取对应令牌:

import org.springframework.security.oauth2.client.web.OAuth2ClientHttpRequestInterceptor;

@HttpExchange(
        url = "/v1",
        accept = MediaType.APPLICATION_JSON_VALUE,
        attributes = @Attribute(name = OAuth2ClientHttpRequestInterceptor.CLIENT_REGISTRATION_ID_ATTRIBUTE, value = "my-client")
)
public interface UserClient {

    @GetExchange("/protectedresource/full")
    User getUserById(@RequestParam Map<String, String> params);

}

若不同方法需使用不同clientId,可在方法级别的@GetExchange/@PostExchange等注解中单独配置attributes。

方式二:方法参数动态传递clientId

在HttpInterface方法中添加@RequestAttribute参数,调用时动态传入clientRegistrationId,适合需要灵活切换clientId的场景:

修改HttpInterface接口

import org.springframework.security.oauth2.client.web.OAuth2ClientHttpRequestInterceptor;

@HttpExchange(
        url = "/v1",
        accept = MediaType.APPLICATION_JSON_VALUE)
public interface UserClient {

    @GetExchange("/protectedresource/full")
    User getUserById(
            @RequestParam Map<String, String> params,
            @RequestAttribute(name = OAuth2ClientHttpRequestInterceptor.CLIENT_REGISTRATION_ID_ATTRIBUTE) String clientId
    );

}

调用时传递clientId

@GetMapping("/lessons")
public User fetchLessons() {
    return userClient.getUserById(Map.of("foo", "bar"), "my-client");
}

方式三:全局配置默认clientId

若HttpInterface仅需固定使用一个clientId,可在构建RestClient时设置全局默认属性,避免重复配置:

import org.springframework.security.oauth2.client.ClientRegistrationId;

@Configuration
public class UserClientConfig {

    private final RestClient restClient;

    public UserClientConfig(OAuth2AuthorizedClientManager authorizedClientManager, RestClient.Builder restClientBuilder) {
        OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
        this.restClient = restClientBuilder
                .requestInterceptor(interceptor)
                .baseUrl("https://host.com")
                // 设置全局默认clientRegistrationId
                .defaultRequest(request -> request.attributes(ClientRegistrationId.clientRegistrationId("my-client")))
                .build();
    }

    @Bean
    public UserClient userClient() {
        RestClientAdapter adapter = RestClientAdapter.create(restClient);
        return HttpServiceProxyFactory.builderFor(adapter)
                .build()
                .createClient(UserClient.class);
    }

}

配置后,该HttpInterface发起的所有请求都会自动携带my-client的clientRegistrationId属性,无需额外设置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 23:37:03