Feign 和 Spring Security 5 - 客户端凭据 [英] Feign and Spring Security 5 - Client Credentials
问题描述
我正在尝试从 Feign 客户端应用程序调用一些由 client_credentials 授权类型保护的后端系统.
I am trying to invoke some backend system which is secured by a client_credentials grant type from a Feign client application.
可以使用以下 curl 结构检索来自后端系统的访问令牌(仅作为示例):
The access token from the backend system can be retrieved with the following curl structure (just as an example):
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 然后应在标头中设置,以用于对后端系统的后续业务调用.
This accessToken should then be set in a header to subsequent business calls to the backend system.
所以现在我的问题是,如何使用 Feign 和 Spring Boot Security 5 来实现这一点.经过一番研究,我找到了这个解决方案(不起作用):
So now my question is, how to implement this using Feign and Spring Boot Security 5. After some research I come to this solution (which doesn't work):
- 在 application.yml 中定义我的客户端:
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
- 创建 OAuth2AuthorizedClientManager Bean 以授权(或重新授权)OAuth 2.0 客户端:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
return authorizedClientManager;
}
- 创建一个使用 OAuth2AuthorizedClientManager 的 Feign 请求拦截器:
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";
}
};
}
}
- 创建一个使用拦截器的 FeignConfig:
public class FeignClientConfig {
@Bean
public OAuthRequestInterceptor repositoryClientOAuth2Interceptor(OAuth2AuthorizedClientManager manager) {
return new OAuthRequestInterceptor(manager);
}
}
- 这是我的 Feign 客户:
@FeignClient(name = "BackendRepository", configuration = FeignClientConfig.class, url = "${BACKEND_URL}")
public interface BackendRepository {
@GetMapping(path = "/healthChecks", produces = MediaType.APPLICATION_JSON_VALUE)
public Info healthCheck();
}
运行此代码时,出现错误:
When running this code, I get the error:
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 正在使用基本身份验证请求身份验证端点.虽然我从来没有设置过.
Debugging the code it looks like the DefaultClientCredentialsTokenResponseClient is requesting the auth endpoint using Basic Authentication. Although I never set this up.
任何建议我能做什么?也许有一种完全不同的方法来做到这一点.
Any advise what I can do? Maybe there is a completely different approach to do this.
推荐答案
要与 Spring Security 5 和 Feign 配合使用,您需要具备
For this to work with Spring Security 5 and Feign you need to have
- 一个有效的 Spring Security 配置
- 一个 Feign 拦截器
- 使用该拦截器的 Feign 配置
- 工作 Spring 安全配置
在这里,我们将为您的 oauth2 客户端凭据
注册一个通用的 internal-api
客户端.您可以在此处指定 client-secret
、client-secret
、scopes
和 grant type
.所有基本的 Spring Security 5 东西.这还涉及设置提供程序(这里我使用了一个名为yourprovider"的自定义 OpenID Connect 提供程序
Here we will register a generic internal-api
client for your oauth2 client credentials
. This is where you specify the client-secret
,client-secret
, scopes
and grant type
.
All basic Spring Security 5 stuff. This also involves setting up a provider (here I am using a custom OpenID Connect provider called "yourprovider"
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
Next you need your feign config. This will use a 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 :
And a RequestInterceptor that looks like this :
OAuth2AuthorizedClientManager
是一个可以在配置中配置的 bean
The OAuth2AuthorizedClientManager
is a bean that you can configure in your Configuration
public OAuth2AuthorizedClientManager authorizedClientManager(final ClientRegistrationRepository clientRegistrationRepository, final OAuth2AuthorizedClientService authorizedClientService) {
return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
}
OAuth2AuthorizeRequest
由上面的 Feign 配置提供.oAuth2AuthorizedClientManager
可以授权 oAuth2AuthorizeRequest
,获取访问令牌,并将其作为 Authorization
标头提供给底层服务
The OAuth2AuthorizeRequest
is provided by the Feign Configuration above.
The oAuth2AuthorizedClientManager
can authorize the oAuth2AuthorizeRequest
, get you the access token, and provide it as an Authorization
header to the underlying service
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 和 Spring Security 5 - 客户端凭据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!