Describe the bug spring boot version: 2.5.5 spring cloud version: 2020.0.4
Configuration Info:
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: FULL
notify-service:
requestInterceptors:
- example.feign.BearerAuthRequestInterceptor
circuitbreaker:
enabled: true
RequestInterceptor :
public class BearerAuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
SecurityUtils.getCurrentUserJWT().ifPresent(
token -> template.header("Authorization", String.format("Bearer %s", token))
);
}
}
After setting feign.circuitbreaker.enabled=true, check the log ,No Authorization Header information is included
[NotifyClient#createNotify] ---> POST http://notify-service/createNotify HTTP/1.1
2021-10-17 17:23:21.530 DEBUG 15069 --- [pool-2-thread-1] c.w.lab.skybrid.client.NotifyClient: [NotifyClient#createNotify] Content-Length: 495
2021-10-17 17:23:21.530 DEBUG 15069 --- [pool-2-thread-1] c.w.lab.skybrid.client.NotifyClient: [NotifyClient#createNotify] Content-Type: application/json
When setting feign.circuitbreaker.enabled=false, it works as expected
[NotifyClient#createNotify] ---> POST http://notify-service/createNotify HTTP/1.1
2021-10-17 17:28:29.526 DEBUG 18552 --- [nio-8080-exec-1] c.w.lab.skybrid.client.NotifyClient : [NotifyClient#createNotify] Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjMsImlhdCI6MTYzNDA0NzE4NX0.l4VN7PnLGYd0TYAFer-1jNrrqE2vRDKqVAJ1GfG6Bqg
2021-10-17 17:28:29.526 DEBUG 18552 --- [nio-8080-exec-1] c.w.lab.skybrid.client.NotifyClient : [NotifyClient#createNotify] Content-Length: 495
2021-10-17 17:28:29.526 DEBUG 18552 --- [nio-8080-exec-1] c.w.lab.skybrid.client.NotifyClient : [NotifyClient#createNotify] Content-Type: application/json
Comment From: OlgaMaciaszek
Hello, @oursy. Thanks for reporting the issue. Please provide a minimal, complete, verifiable example that reproduces the issue.
Comment From: oursy
@OlgaMaciaszek Hi, I tried the smallest example locally and found that the cause of the problem was
SecurityUtils.getCurrentUserJWT().ifPresent(
token -> template.header("Authorization", String.format("Bearer %s", token))
);
public static Optional<String> getCurrentUserJWT() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional
.ofNullable(securityContext.getAuthentication())
.filter(authentication -> authentication instanceof JwtAuthenticationToken)
.map(authentication -> ((JwtAuthenticationToken) authentication).getToken().getTokenValue());
}
The reason for the problem is that the object obtained in the line SecurityContextHolder.getContext() is Null.
When using Hystrix, you can use the configuration hystrix.shareSecurityContext: true parameter to share the security context.
Is there a similar parameter configuration for circuitbreaker?
Comment From: OlgaMaciaszek
CircuitBreaker is an abstraction on top of various implementations. You should be able to use whichever setup the downstream library provides, so if you are using Hystrix, their setup, Resillience4J, their setup, etc., so hopefully you can find a solution with the implementation you've chosen. If you still need help with the CircuitBreakers setup that are not directly related to the OpenFeign integration, please create a separate issue in the https://github.com/spring-cloud/spring-cloud-circuitbreaker repo.
Comment From: oursy
The following code worked for me .
@Bean
public Customizer<Resilience4jBulkheadProvider> defaultBulkheadCustomizer() {
return provider -> provider.configureDefault(id -> new Resilience4jBulkheadConfigurationBuilder()
.bulkheadConfig(BulkheadConfig.custom().build())
.threadPoolBulkheadConfig(ThreadPoolBulkheadConfig.custom()
.contextPropagator(new SecurityBulkheadConfig())
.build())
.build()
);
}
public class SecurityBulkheadConfig implements ContextPropagator<SecurityContext> {
@Override
public Supplier<Optional<SecurityContext>> retrieve() {
return new Supplier<>() {
@Override
public Optional<SecurityContext> get() {
return Optional.of(SecurityContextHolder.getContext());
}
};
}
@Override
public Consumer<Optional<SecurityContext>> copy() {
return new Consumer<Optional<SecurityContext>>() {
@Override
public void accept(Optional<SecurityContext> t) {
t.ifPresent(SecurityContextHolder::setContext);
}
};
}
@Override
public Consumer<Optional<SecurityContext>> clear() {
return t -> t.ifPresent(t1 -> SecurityContextHolder.clearContext());
}
}