Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEIGN + OAUTH2 calls from another thread not propagating security #1330

Closed
bilak opened this issue Sep 9, 2016 · 14 comments
Closed

FEIGN + OAUTH2 calls from another thread not propagating security #1330

bilak opened this issue Sep 9, 2016 · 14 comments

Comments

@bilak
Copy link

bilak commented Sep 9, 2016

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:

  1. get access token with command curl -XPOST -u demo:demo localhost:9090/oauth/token -d grant_type=password -d username=user -d password=user
  2. call service within current thread curl -H 'Authorization: Bearer [token from step 1]' localhost:8090/entries/current-thread
  3. 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)?

@daniellavoie
Copy link
Contributor

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
Copy link
Author

bilak commented Sep 10, 2016

Hi, I've tried with M1, but that didn't resolved my issue :(

@daniellavoie
Copy link
Contributor

I'll check out your sample project tomorrow and give a try on finding out what's wrong.

@daniellavoie
Copy link
Contributor

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
Copy link
Author

bilak commented 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
Copy link
Contributor

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 within SecurityContextConcurrencyStrategy.

Good luck.

@bilak
Copy link
Author

bilak commented 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 method wrapCallable 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
Copy link
Contributor

Tell me if I am wrong but I have the feeling that 3 threads are involved in your current setup.

  1. Spring MVC Thread handling the request
  2. Your homemade Callable that you wrapped with 'DelegatingSecurityContextCallable'.
  3. Finally, the Hyxtrix thread handling the feign call triggered by the thread from step 2.

Considering this setup, if you include in your classpath spring-boot-starter-security and set hystrix.shareSecurityContext to true, Security Context should be transfered seamlessly from thread 2 to thread 3. 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 import spring-boot-starter-security and activate hystrix.shareSecurityContext for a test purpose.

@bilak
Copy link
Author

bilak commented 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
Copy link
Contributor

You should not remove the wrapper callable as you are in a 3 thread hierarchy setup. the hystrix.shareSecurityContext property will transfer SecurityContext from thread 2 to 3. You still need your wrapper to transfer from thread 1 to 2.

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
Copy link
Author

bilak commented 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 and OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE but I'm not OAuth2 expert too.

@bilak
Copy link
Author

bilak commented 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
Copy link
Contributor

@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
Copy link

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:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants