Description
Hi,
I'm spending hard times with setup of feign client while I want it to call service from not current thread.
First attempt was to call feign client from current thread. To do this I've added RequestContextFilter but that didn't helped. Then I found this thread and I configured property feign.hystrix.enabled: false
and finally I was able to call feign client from current thread.
Now for the issue:
For security context propagation I'm using DelegatingSecurityContextAsyncTaskExecutor as I hope this executor is suitable to do so. In this executor I'm executing Callable
which calls feignClient, but I allways get this exception:
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:192) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at com.sun.proxy.$Proxy106.getAccessToken(Unknown Source) ~[na:na]
at org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor.getToken(OAuth2FeignRequestInterceptor.java:124) ~[spring-cloud-security-1.1.2.RELEASE.jar:1.1.2.RELEASE]
at org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor.extract(OAuth2FeignRequestInterceptor.java:112) ~[spring-cloud-security-1.1.2.RELEASE.jar:1.1.2.RELEASE]
at org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor.apply(OAuth2FeignRequestInterceptor.java:100) ~[spring-cloud-security-1.1.2.RELEASE.jar:1.1.2.RELEASE]
at feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:154) ~[feign-core-8.16.2.jar:8.16.2]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88) ~[feign-core-8.16.2.jar:8.16.2]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-8.16.2.jar:8.16.2]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-8.16.2.jar:8.16.2]
at com.sun.proxy.$Proxy107.getDefinedEntries(Unknown Source) ~[na:na]
at com.github.bilak.poc.hystrix_oauth2_feign.api.rest.SampleController$SampleServiceClientCaller.call(SampleController.java:69) ~[classes/:na]
at com.github.bilak.poc.hystrix_oauth2_feign.api.rest.SampleController$SampleServiceClientCaller.call(SampleController.java:58) ~[classes/:na]
at org.springframework.security.concurrent.DelegatingSecurityContextCallable.call(DelegatingSecurityContextCallable.java:86) ~[spring-security-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_77]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_77]
here is my sample project. When you start both services follow this steps:
- get access token with command curl -XPOST -u demo:demo localhost:9090/oauth/token -d grant_type=password -d username=user -d password=user
- call service within current thread curl -H 'Authorization: Bearer [token from step 1]' localhost:8090/entries/current-thread
- call service within another thread curl -H 'Authorization: Bearer [token from step 1]' localhost:8090/entries/another-thread
Can someone point me to correct setup for feign and oauth2 to have working calls? Is it also possible to enable hystrix for this (use SEMAPHORE or something else)?
Activity
daniellavoie commentedon Sep 9, 2016
Hi there, checkout the reference doc on 1.2.0.M1. A new property
hystrix.shareSecurityContext=true
will auto configure an Hyxtrik hook that will propagate the security context to the thread of your Hystrix command. Let me know if it fits your need.bilak commentedon Sep 10, 2016
Hi, I've tried with M1, but that didn't resolved my issue :(
daniellavoie commentedon Sep 11, 2016
I'll check out your sample project tomorrow and give a try on finding out what's wrong.
daniellavoie commentedon Sep 11, 2016
I've checked out your project and I don't understand why you want to make a call with Feign from a home made executor.
By default, Feign operations are already wrapped in a seperate Hyxtrix thread. If you keep this simple setup, you can activate the
hystrix.shareSecurityContext=true
property so the SecurityContext built from the Rest endpoint gets delegated to the one handling the Feign operation. Any Feign interceptor declared would have access to the Security Context.You can checkout this unit test here. The test actually sets up a Feign Client with an interceptor that generates a http header based on the username of the SecurityContext. The interceptor is invoke in a separate thread by Hystrix, proving that it is properly delegated automatically.
Explain your use case a bit more so I can guide you more efficiently.
Regards,
Daniel
bilak commentedon Sep 12, 2016
@daniellavoie I need to execute it in another thread, because the application is build in this style. Application "rest-api" code is executed within main thread and another threads are used to do validation and call another services. Code is based on AxonFramework if that helps you. My application only demonstrates the usage of code. If you need more info, let me know. Thanks
daniellavoie commentedon Sep 12, 2016
The
DelegatingSecurityContextCallable
should help you to transfer the security context to a callable. Try it out. Spring Security / Hystrix bridge is using it.You can inspire yourself of this PR. It leverages the
DelegatingSecurityContextCallable
withinSecurityContextConcurrencyStrategy
.Good luck.
bilak commentedon Sep 12, 2016
@daniellavoie try to look at this commit. I've just wrapped the client call in another callable and added
RequestContextHolder.setRequestAttributes(RequestContextHolder.currentRequestAttributes(), true);
to the constructors what solves my issue.Now I just want to know if it's possible to somehow create this with some Hystrix configuration. I've tried to extend
HystrixConcurrencyStrategy
and override the methodwrapCallable
but that didn't solved my issue, because this method is executed from another thread and I'm getting also exception No thread-bound request found ...Thanks
daniellavoie commentedon Sep 12, 2016
Tell me if I am wrong but I have the feeling that 3 threads are involved in your current setup.
Considering this setup, if you include in your classpath
spring-boot-starter-security
and sethystrix.shareSecurityContext
to true, Security Context should be transfered seamlessly from thread2
to thread3
. There is currently an open issue (#1336) stating that this transfer would not work if Spring Security is not part of the classpath. Considering you might only be using Spring Cloud OAuth, I might have to improve this mechanism. I would ask you to importspring-boot-starter-security
and activatehystrix.shareSecurityContext
for a test purpose.bilak commentedon Sep 12, 2016
Ok so I removed the "wrapper" callable and added spring-boot-starter-security to the dependencies, enabled hystrix.shareSecurityContext=true. But unfortunately I've got again No thread-bound request found. There could be probably possibility in hystrix to pass the current request attributes to child threads, something as I wrote in wrapper class ...
RequestContextHolder.setRequestAttributes(RequestContextHolder.currentRequestAttributes(), true);
here is branch with current code.
daniellavoie commentedon Sep 12, 2016
You should not remove the wrapper callable as you are in a 3 thread hierarchy setup. the
hystrix.shareSecurityContext
property will transfer SecurityContext from thread2
to3
. You still need your wrapper to transfer from thread1
to2
.I am no expert on Spring Cloud OAuth so I will checkout the implementation to find if the
DelegatingSecurityContextCallable
transfers everything you are expecting. I fear that a custom one specially designed for the attributes of Spring Cloud OAuth might need to be implemented.bilak commentedon Sep 12, 2016
maybe @dsyer can help us here to specify which attributes needs to be transfered. Then we can maybe create something like DelegatinOAuth2SecurityContext classes.
I think the attributes should be
OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE
andOAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE
but I'm not OAuth2 expert too.bilak commentedon Sep 13, 2016
I've resolved the issue using custom
HystrixConcurrencyStrategy
and custom ExecutorService here. However I can't figure out how to do this for Runnable and if it's even possible. Try to look at/entries/runnable
endpoint.marcingrzejszczak commentedon Sep 10, 2018
@bilak is it still an issue with the latest release trains? If that's the case can you please open a new issue in the https://github.com/spring-cloud/spring-cloud-openfeign/ project?
toneeraj commentedon Nov 20, 2018
By default, the Spring Security Authentication is bound to a ThreadLocal – so, when the execution flow runs in a new thread with (Observable.zip) that’s not going to be an authenticated context. To prevent that behaviour, we need to enable the SecurityContextHolder.MODE_INHERITABLETHREADLOCAL strategy: