If your JWT token provider is OAuth 2.0 compliant, you can configure the OAuth2FeignRequestInterceptor, with an OAuth2ProtectedResourceDetails object. This object is the base class for all OAuth 2.0 grant type information. In your case, I recommend using ResourceOwnerPasswordResourceDetails instead. This will allow you to configure an interceptor using a username and password.
@Configuration
public class OAuth2RequestInterceptorConfiguration {
@Bean
public OAuth2FeignRequestInterceptor requestInterceptor() {
OAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
OAuth2ProtectedResourceDetails resourceDetails =
new ResourceOwnerPasswordResourceDetails();
resourceDetails.setUsername("username");
resourceDetails.setPassword("password");
return new OAuth2FeignRequestInterceptor(clientContext, resourceDetails);
}
}
For other cases, you will need to create your own RequestInterceptor
public class MyRequestInterceptor implements RequestInterceptor {
private String jwt;
private LocalDateTime expirationDate;
@Override
public void apply(RequestTemplate requestTemplate) {
/* validate and refresh your token, this sample is not thread safe */
if (LocalDateTime.now().isAfter(expirationDate)) {
requestToken();
}
/* use the token */
requestTemplate.header("Authorization: Bearer " + this.jwt);
}
}
Answer from Kevin Davis on Stack OverflowIf your JWT token provider is OAuth 2.0 compliant, you can configure the OAuth2FeignRequestInterceptor, with an OAuth2ProtectedResourceDetails object. This object is the base class for all OAuth 2.0 grant type information. In your case, I recommend using ResourceOwnerPasswordResourceDetails instead. This will allow you to configure an interceptor using a username and password.
@Configuration
public class OAuth2RequestInterceptorConfiguration {
@Bean
public OAuth2FeignRequestInterceptor requestInterceptor() {
OAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
OAuth2ProtectedResourceDetails resourceDetails =
new ResourceOwnerPasswordResourceDetails();
resourceDetails.setUsername("username");
resourceDetails.setPassword("password");
return new OAuth2FeignRequestInterceptor(clientContext, resourceDetails);
}
}
For other cases, you will need to create your own RequestInterceptor
public class MyRequestInterceptor implements RequestInterceptor {
private String jwt;
private LocalDateTime expirationDate;
@Override
public void apply(RequestTemplate requestTemplate) {
/* validate and refresh your token, this sample is not thread safe */
if (LocalDateTime.now().isAfter(expirationDate)) {
requestToken();
}
/* use the token */
requestTemplate.header("Authorization: Bearer " + this.jwt);
}
}
You can try with something like:
public class FeignConfiguration {
@Value("${security.jwt.token}")
private String jwtToken;
@Bean
public RequestInterceptor requestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("Authorization", jwtToken);
}
}
}
You don't really need your own implementation of the FeignRequestInterceptor as there is already BasicAuthRequestInterceptor in the feign.auth package that does exactly the same.
That being said, you basically have almost everything set up already. All is left to do is to define the basicAuthRequestInterceptor bean with specific username and password:
@Bean
public RequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("username", "password");
}
I know the thread is a bit old but wanted to give some explanation on what's happening here.
If you'd like to customize your Feign requests, you can use a RequestInterceptor. This can be a custom implementation or you can reuse what's available in the Feign library, e.g. BasicAuthRequestInterceptor.
How to register it? Well, there are 2 ways to do it depending on how you use Feign.
If you're using plain Feign without Spring, then you gotta set the interceptor to the Feign builder. An example is here.
Feign.builder()
.requestInterceptor(new MyCustomInterceptor())
.target(MyClient.class, "http://localhost:8081");
If you're using Spring Cloud OpenFeign and you use the @FeignClient annotation to construct your clients, then you have to create a bean from your RequestInterceptor by either defining it as a @Component or as a @Bean in one of your @Configuration classes. Example here.
@Component
public class MyCustomInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// do something
}
}
Also, you can check out one of my articles in this topic, maybe that clears it up better: Customizing each request with Spring Cloud Feign
Feign doesn't use a RestTemplate so you'd have to find a different way. If you create a @Bean of type feign.RequestInterceptor it will be applied to all requests, so maybe one of those with an OAuth2RestTemplate in it (just to manage the token acquisition) would be the best option.
this is my solution, just to complement the another answer with the source code, implementing the interface feign.RequestInterceptor
@Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)
SecurityContextHolder.getContext().getAuthentication().getDetails();
requestTemplate.header("Authorization", "bearer " + details.getTokenValue());
}
};
}
According documentation need use AuthorizedClientServiceOAuth2AuthorizedClientManager instead of DefaultOAuth2AuthorizedClientManager
When operating outside of the context of a HttpServletRequest, use AuthorizedClientServiceOAuth2AuthorizedClientManager instead.
So. I was playing with your solution in my free time. And found the simple solution:
just add SecurityContextHolder.getContext().authentication principle to your code OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId(appClientId).build();
Should be like this:
val request = OAuth2AuthorizeRequest
.withClientRegistrationId("keycloak") // <-- here your registered client from application.yaml
.principal(SecurityContextHolder.getContext().authentication)
.build()
Used packages:
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.cloud:spring-cloud-starter-openfeign")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
application.yaml:
spring:
security:
oauth2:
client:
registration:
keycloak: # <--- It's your custom client. I am using keycloak
client-id: ${SECURITY_CLIENT_ID}
client-secret: ${SECURITY_CLIENT_SECRET}
authorization-grant-type: client_credentials
scope: openid # your scopes
provider:
keycloak: # <--- Here Registered my custom provider
authorization-uri: ${SECURITY_HOST}/auth/realms/${YOUR_REALM}/protocol/openid-connect/authorize
token-uri: ${SECURITY_HOST}/auth/realms/${YOUR_REALM}/protocol/openid-connect/token
feign:
compression:
request:
enabled: true
mime-types: application/json
response:
enabled: true
client.config.default:
connectTimeout: 1000
readTimeout: 60000
decode404: false
loggerLevel: ${LOG_LEVEL_FEIGN:basic}
SecurityConfiguration:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration() : WebSecurityConfigurerAdapter() {
@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
// @formatter:off
http
.authorizeRequests { authorizeRequests ->
authorizeRequests
.antMatchers(HttpMethod.GET, "/test").permitAll() // Here my public endpoint which do logic with secured client enpoint
.anyRequest().authenticated()
}.cors().configurationSource(corsConfigurationSource()).and()
.csrf().disable()
.cors().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.oauth2Client()
// @formatter:on
}
@Bean
fun authorizedClientManager(
clientRegistration: ClientRegistrationRepository?,
authorizedClient: OAuth2AuthorizedClientRepository?
): OAuth2AuthorizedClientManager? {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
.builder()
.clientCredentials()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(clientRegistration, authorizedClient)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
}
FeignClientConfiguration:
private val logger = KotlinLogging.logger {}
class FeignClientConfiguration(private val authorizedClientManager: OAuth2AuthorizedClientManager) {
@Bean
fun requestInterceptor(): RequestInterceptor = RequestInterceptor { template ->
if (template.headers()["Authorization"].isNullOrEmpty()) {
val accessToken = getAccessToken()
logger.debug { "ACCESS TOKEN TYPE: ${accessToken?.tokenType?.value}" }
logger.debug { "ACCESS TOKEN: ${accessToken?.tokenValue}" }
template.header("Authorization", "Bearer ${accessToken?.tokenValue}")
}
}
private fun getAccessToken(): OAuth2AccessToken? {
val request = OAuth2AuthorizeRequest
.withClientRegistrationId("keycloak") // <- Here you load your registered client
.principal(SecurityContextHolder.getContext().authentication)
.build()
return authorizedClientManager.authorize(request)?.accessToken
}
}
TestClient:
@FeignClient(
name = "test",
url = "http://localhost:8080",
configuration = [FeignClientConfiguration::class]
)
interface TestClient {
@GetMapping("/test")
fun test(): ResponseEntity<Void> // Here my secured resource server endpoint. Expect 204 status
}