我正在尝试调用一些后端系统,该系统由 Feign 客户端应用程序的 client_credentials 授予类型保护。
可以使用以下curl结构检索来自后端系统的访问令牌(仅作为示例):
curl --location --request POST '[SERVER URL]/oauth/grant' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: WebSessionID=172.22.72.1.1558614080219404; b8d49fdc74b7190aacd4ac9b22e85db8=2f0e4c4dbf6d4269fd3349f61c151223' \
--data-raw 'grant_type=client_credentials' \
--data-raw 'client_id=[CLIENT_ID]' \
--data-raw 'client_secret=[CLIENT_SECRET]'
{"accessToken":"V29C90D1917528E9C29795EF52EC2462D091F9DC106FAFD829D0FA537B78147E20","tokenType":"Bearer","expiresSeconds":7200}
此 accessToken 应设置在标头中,以供后续对后端系统的业务调用。
所以现在我的问题是,如何使用 Feign 和 Spring Boot Security 5 来实现这一点。 经过一番研究后,我得出了这个解决方案(不起作用):
spring:
security:
oauth2:
client:
registration:
backend:
client-id:[CLIENT_ID]
client-secret: [CLIENT_SECRET]
authorization-grant-type: client_credentials
provider:
backend:
token-uri: [SERVER URL]/oauth/grant
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
return authorizedClientManager;
}
public class OAuthRequestInterceptor implements RequestInterceptor {
private OAuth2AuthorizedClientManager manager;
public OAuthRequestInterceptor(OAuth2AuthorizedClientManager manager) {
this.manager = manager;
}
@Override
public void apply(RequestTemplate requestTemplate) {
OAuth2AuthorizedClient client = this.manager.authorize(OAuth2AuthorizeRequest.withClientRegistrationId("backend").principal(createPrincipal()).build());
String accessToken = client.getAccessToken().getTokenValue();
requestTemplate.header(HttpHeaders.AUTHORIZATION, "Bearer" + accessToken);
}
private Authentication createPrincipal() {
return new Authentication() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptySet();
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getDetails() {
return null;
}
@Override
public Object getPrincipal() {
return this;
}
@Override
public boolean isAuthenticated() {
return false;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
@Override
public String getName() {
return "backend";
}
};
}
}
public class FeignClientConfig {
@Bean
public OAuthRequestInterceptor repositoryClientOAuth2Interceptor(OAuth2AuthorizedClientManager manager) {
return new OAuthRequestInterceptor(manager);
}
}
@FeignClient(name = "BackendRepository", configuration = FeignClientConfig.class, url = "${BACKEND_URL}")
public interface BackendRepository {
@GetMapping(path = "/healthChecks", produces = MediaType.APPLICATION_JSON_VALUE)
public Info healthCheck();
}
运行此代码时,出现错误:
org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse] and content type [text/html;charset=utf-8]
调试代码,看起来 DefaultClientCredentialsTokenResponseClient 正在使用基本身份验证请求身份验证端点。虽然我从来没有设置过这个。
有什么建议我可以做什么吗?也许有一种完全不同的方法来做到这一点。
要与 Spring Security 5 和 Feign 配合使用,您需要具备
在这里,我们将为您的 oauth2
internal-api
注册一个通用 client credentials
客户端。您可以在此处指定 client-id
、client-secret
、scopes
和 grant type
。
所有基本的 Spring Security 5 内容。这还涉及设置提供商(这里我使用名为“yourprovider”的自定义 OpenID Connect 提供商
spring:
security:
oauth2:
client:
registration:
internal-api:
provider: yourprovider
client-id: x
client-secret: y
scope:
- ROLE_ADMIN
authorization-grant-type: client_credentials
provider:
yourprovider:
issuer-uri: yourprovider.issuer-uri
resourceserver:
jwt:
issuer-uri: yourprovider.issuer-uri
接下来你需要你的 feign 配置。这将使用
OAuth2FeignRequestInterceptor
public class ServiceToServiceFeignConfiguration extends AbstractFeignConfiguration {
@Bean
public OAuth2FeignRequestInterceptor requestInterceptor() {
return new OAuth2FeignRequestInterceptor(
OAuth2AuthorizeRequest.withClientRegistrationId("internal-api")
.principal(new AnonymousAuthenticationToken("feignClient", "feignClient", createAuthorityList("ROLE_ANONYMOUS")))
.build());
}
}
还有一个像这样的 RequestInterceptor :
OAuth2AuthorizedClientManager
是一个可以在配置中配置的bean
public OAuth2AuthorizedClientManager authorizedClientManager(final ClientRegistrationRepository clientRegistrationRepository, final OAuth2AuthorizedClientService authorizedClientService) {
return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
}
OAuth2AuthorizeRequest
是由上面的Feign配置提供的。
oAuth2AuthorizedClientManager
可以授权 oAuth2AuthorizeRequest
,获取访问令牌,并将其作为 Authorization
标头提供给底层服务
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
@Inject
private OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager;
private OAuth2AuthorizeRequest oAuth2AuthorizeRequest;
OAuth2FeignRequestInterceptor(OAuth2AuthorizeRequest oAuth2AuthorizeRequest) {
this.oAuth2AuthorizeRequest = oAuth2AuthorizeRequest;
}
@Override
public void apply(RequestTemplate template) {
template.header(AUTHORIZATION,getAuthorizationToken());
}
private String getAuthorizationToken() {
final OAuth2AccessToken accessToken = oAuth2AuthorizedClientManager.authorize(oAuth2AuthorizeRequest).getAccessToken();
return String.format("%s %s", accessToken.getTokenType().getValue(), accessToken.getTokenValue());
}
}
我对 Feign 和 OAuth2 非常有经验,我花了几个小时才找到如何做到这一点。 首先,假设我的应用程序基于最新的 Spring 库,因此我使用以下依赖项(spring-cloud-starter-openfeign 的托管版本是 3.0.0)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
在我的 application.properties 中我有以下内容
security.oauth2.client.access-token-uri=https://api.twitter.com/oauth2/token
security.oauth2.client.client-id=my-secret-twitter-id
security.oauth2.client.client-secret=my-secret-twitter-secret
security.oauth2.client.grant-type=client_credentials
最后是我的配置bean
package es.spanishkangaroo.ttanalyzer.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import feign.RequestInterceptor;
@Configuration
public class FeignClientConfiguration {
@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
@Bean
public RequestInterceptor oauth2FeignRequestInterceptor(){
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());
}
@Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
}
那么 Feign 客户端就这么简单
package es.spanishkangaroo.ttanalyzer.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import es.clovelly.ttanalyzer.model.Trends;
@FeignClient(name = "twitterClient", url = "https://api.twitter.com/1.1/")
public interface TwitterClient {
@GetMapping("/trends/place.json")
Trends[] getTrendsById(@RequestParam Long id);
}
您可能已经注意到,代码会在客户端调用之前自动获取令牌(不记名令牌)。如果您使用的是不会过期的不记名令牌,您可以使用类似的东西
@Bean
public OAuth2ClientContext oAuth2ClientContext() {
DefaultOAuth2ClientContext context = new DefaultOAuth2ClientContext();
context.setAccessToken(bearerToken);
return context;
}
我知道这是一个非常老的问题,但是如果有人使用 spring boot 3.x 版本并面对这个问题,我从here找到了简单的解决方案。就放吧
spring.cloud.openfeign.oauth2.enabled=true
在你的 application.properties 文件中,Spring Boot 本身创建了必要的 bean!
我尝试过你的方法。不幸的是没有成功。但这对我有用:Spring cloud Feign OAuth2 请求拦截器不起作用。看起来我现在使用了很多掠夺,但至少它确实有效。