如何将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

