spring-cloud / spring-cloud-netflix

Integration with Netflix OSS components
http://cloud.spring.io/spring-cloud-netflix/
Apache License 2.0
4.87k stars 2.44k forks source link

How components such use Zuul+Feign+Eureka+Oauth2? #675

Closed Dreampie closed 8 years ago

spencergibb commented 8 years ago

You need to provide more information. Are you having a problem? If so describe it.

Dreampie commented 8 years ago

I want use feign for eureka client,but i don't know how can i use zuul +oauth2 for feign.RestTemplate need more and more coding.feign easier than RestTemplate

dsyer commented 8 years ago

N.B. we have a feign interceptor for oauth2 now (https://github.com/spring-cloud/spring-cloud-security/issues/56), but it isn't autoconfigured so you need to add a bean in a custom @FeignClient configuration.

Dreampie commented 8 years ago

Thank you!

miguelfgar commented 8 years ago

Hi, I'm also trying to set FeignClient with OAuth2 to implement "Relay Token". I just want FeignClient to relay / propagate the OAuth2 Token that comes from ZuulProxy (SSO Enabled). I use Spring 1.3.1-RELEASE and Spring Cloud Brixton.M4.

According to @dsyer previous comment and posts by @joaoevangelista in other posts, I have added an interceptor in a custom @FeignClient configuration:

import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.client.OAuth2ClientContext; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;

import feign.RequestInterceptor;

@Configuration public class FeignClientConfiguration {

@Value("${security.oauth2.client.userAuthorizationUri}")
private String authorizeUrl;

@Value("${security.oauth2.client.accessTokenUri}")
private String tokenUrl;

@Value("${security.oauth2.client.client-id}")
private String clientId;

// See https://github.com/spring-cloud/spring-cloud-netflix/issues/675
@Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext){
    return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource());
}

@Bean
protected OAuth2ProtectedResourceDetails resource() {
    AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
    resource.setAccessTokenUri(tokenUrl);
    resource.setUserAuthorizationUri(authorizeUrl);
    resource.setClientId(clientId);
    // TODO: Remove this harcode 
    resource.setClientSecret("secret");
    return resource;
}   

}

And I add the configuration to my @FeignClient like that:

@FeignClient(name = "car-service", configuration = FeignClientConfiguration.class)
interface CarClient {               
    @RequestMapping(value = "car-service/api/car", method = GET)
    List<CarVO> getAllCars();
}   

The application starts but when I use the Feign Client from my service I get:

2016-01-08 13:14:29.757 ERROR 3308 --- [nio-9081-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [/user-service] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: getAllCars failed and no fallback available.] with root cause

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.2.4.RELEASE.jar:4.2.4.RELEASE] at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41) ~[spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE] ......

I want my application / microservice (the one that uses the @FeingClient to call the other application / microservice) to be STATELESS. However, I have tried both, with security.sessions=STATELESS (SpringBoot default) and security.sessions=ALWAYS (just to try). In both cases I got the same exception.

Having a look at the code I have seen that the OAuth2ClientContext is saved in Session (Session scoped bean). How does it work when you want to implement a STATELESS OAuth2 enabled application / microservice? Precisely this is one of the big advantages of using OAuth2 in my current scenario. However, as I said, the result was the same enabling sessions.

Can someone help with this, please?

Thanks so much! :-)

dsyer commented 8 years ago

If you are using Feign with Ribbon it will execute requests in a background thread by default, so the security context and the current http request (and session) are not available. There are 2 separate questions here: whether to background the remote call, and whether to use sessions to store tokens. Try not to obsess over state and sessions and accept the advice of the Spring team (use sessions for security in a browser based app). If your app is not browser based you have to do more work (but nothing you said leads me to believe that yet). The background thread thing is a ribbon/Hystrix feature which you can work around by setting the isolation level to SEMAPHORE, but potentially we could maybe provide a bridge for the context in Spring Cloud (assuming it isn't already there because I did see some changes recently and haven't caught up yet).

miguelfgar commented 8 years ago

Hi @dsyer,

Thanks so much for your answer and help.

  1. I agree with your recommendation of using sessions for security in browser based apps and we are doing it. At the end of the day security becomes 'stateful' when you want to prevent CSRF attacks, for instance as you need the session to keep the token. Currently, I implement Token Relay and SSO using ZuulProxy inspired by your "SSO with OAuth2" blog series (ZuulProxy annotated with @EnableOAuth2Sso). As far as I understand, this approach makes the client less complex (let's say an HTML5 app using the browser as platform) in the regard that the one acting as a OAuth2 client is ZuulProxy (with authorization_code grant type). So in summary, we have session based security (JSESSIONID) in the front side (between the UI client and Zuul) but with the advantage that in the back end side (behind Zuul, between Zuul and the Resource Servers (microservices)) only the OAuth2 token is used (relay by Zuul). What I intend to have 'stateless' are the Resource Servers that will just publish a REST API with the intention to be able to scale them with Docker and being able to use Eureka, Ribbon, etc.. for the discovery and load balancing part to achieve this. But yes, in this scenario we use sessions but just for the security. Does it make sense?
  2. We are planning to have non-browser based apps, for instance a mobile native app. Yes, I guess that's is a tricky part as we probably need to think about using a different grant_type (maybe user and password, for instance) as we will not want in this case the user to have to go to a browser to put his / her credentials in a page that the auth server provides. I guess we should provide a different solution for this sort of clients than the aforementioned scenario (SSO with token relay using Zuul) which would be OK for browser based apps.
  3. About Hystrix isolation levels to be able to use OAuth2FeignRequestInterceptor I have this issue opened: https://github.com/spring-cloud/spring-cloud-security/issues/89 This overlaps a bit with this point of the conversation. Sorry for that, It was not on purpose, I opened the issue later as a product of the process of understanding things better :-). If it's OK for you we can continue this part of the conversation there. As a summary I guess the bridge for the context in Spring Cloud might be a great idea as in many cases you will still want to use THREAD isolation level for perfomance reasons but requiring the context (with the OAuth2FeingRequestInterceptor). I'm going to check to see if it's included in the snapshots already.

Thanks again!

miguelfgar commented 8 years ago

Hi @dsyer,

Do you know if the "bridge" you made reference above when you say "potentially we could maybe provide a bridge for the context in Spring Cloud (assuming it isn't already there because I did see some changes recently and haven't caught up yet)." is available? (even if it is in a snapshot)

I have not been able to identify the code belonging to this "bridge". If so, could you provide me with the name of the class to find it? This bridge will come in very handy from my point of view.

Once again, thanks for your help

dsyer commented 8 years ago

If you didn't find it my guess is it doesn't exist yet. You can implement something using the async support in Spring Security.

loesak commented 8 years ago

This just bit me and i was having trouble using this provided solution. I think what is not clear here is that this solution will not work when using @EnableOAuth2Sso. The reason for this is having this annotation triggers the use of @EnableOAuth2Client which initiates OAuth2ClientConfiguration. This then causes OAuth2RestOperationsConfiguration#RequestScopedConfiguration to not be used which is the configuration that creates the DefaultOAuth2ClientContext based off the current Principal in the SecurityContextHolder. Seems a bit counter intuitive as the purpose of using @EnableOAuth2Sso is to forward on the received bearer token and the documentation says that it can be used with OAuth resource servers.

spencergibb commented 8 years ago

Closing this due to inactivity. Please re-open if there's more to discuss.

bryantp commented 7 years ago

I am also using this with the EnableOAuth2Sso annotation from a Client to a Resource. Resource to Resource seems to work fine with Feign. My client throws the

java.lang.IllegalStateException: No thread-bound request found

exception using Feign though.

wcandml commented 7 years ago

@spencergibb @dsyer Enabled HTTPS in OAuth2 encounter some problems, can you help me look at it?http://stackoverflow.com/questions/44060566/when-i-switch-from-http-to-https-auth2-client-can-get-the-token-but-can-not-ac/44060595#44060595

I need your help, thanks!!!