具有OAuth2FeignRequestInterceptor的Spring @FeignClient无法正常工作

时间:2016-01-08 12:45:08

标签: spring spring-boot spring-cloud spring-security-oauth2

我正在尝试使用OAuth2设置FeignClient来实现“Relay Token”。我只想让FeignClient中继/传播来自ZuulProxy(启用SSO)的OAuth2令牌。 我使用 Spring 1.3.1-RELEASE Spring Cloud Brixton.M4

我在自定义@FeignClient配置中添加了一个拦截器:

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;
}   
}

我将配置添加到我的@FeignClient中:

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

应用程序启动,但当我从我的服务中使用Feign客户端时,我得到:

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]

我希望我的应用程序/微服务(使用@FeingClient调用其他应用程序/微服务的应用程序/微服务)为STATELESS。但是,我已经尝试了两种方法,其中security.sessions = STATELESS(SpringBoot默认值)和security.sessions = ALWAYS(只是为了尝试)。 在这两种情况下,我都有同样的例外。

看一下我看到的代码,OAuth2ClientContext保存在Session(Session scoped bean)中。当您想要实现无状态OAuth2的应用程序/微服务时,它是如何工作的?准确地说,这是在我当前场景中使用OAuth2的一大优势。但是,正如我所说,结果是相同的启用会话。

有人可以帮帮忙吗?

非常感谢! : - )

3 个答案:

答案 0 :(得分:4)

我发现问题在于Hystrix强制在另一个线程中执行代码,因此您无法访问请求/会话范围的bean。 我正在使用@ FeignClient并启用了Hystrix。当我使用feign.hystrix.enabled:false禁用Hystrix时 从微服务A到微服务B的调用中继令牌(使用OAuth2FeignRequestInterceptor)工作正常。

但是,希望能够保持Hystrix的使用。 我已经看到在这篇文章中有一个新的模块改进了Hystrix - Feign(feign-hystrix模块):

Does Spring Cloud Feign client call execute inside hystrix command?

但是,我没有看到如何使用feign-hystrix正确进行设置,我无法找到一个例子。请你帮忙解决这个问题,或者使用feign-hystrix提供一个例子吗?

非常感谢!

答案 1 :(得分:2)

我不确定我是否理解正确,但以下内容对我有用。

请参阅https://jfconavarrete.wordpress.com/2014/09/15/make-spring-security-context-available-inside-a-hystrix-command/

基本上,本教程展示了如何使用额外的&#34;插件来设置/扩充hystrix&#34;所以安全上下文通过threadlocal变量

在hystrix包装调用中可用

使用此设置,您需要做的就是定义一个假装请求拦截器,如下所示:

@Bean
public RequestInterceptor requestTokenBearerInterceptor() {

    return new RequestInterceptor() {
        @Override
        public void apply(RequestTemplate requestTemplate) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
            requestTemplate.header("Authorization", "Bearer " + details.getTokenValue());                   
        }
    };
}

通过此设置,请求中包含的令牌可供假想请求拦截器使用,因此您可以使用来自经过身份验证的用户的令牌在假想请求上设置Authorization标头。

另请注意,通过这种方法,您可以保持SessionManagementStrategy&#34; STATELESS&#34;因为没有数据必须存储&#34;在服务器端

答案 2 :(得分:0)

在您用作功能区客户端时使用此代码和注释重新配置,而不是在这里,我们将使用oauth2restTemplate

impl InitCmd {
    /// Run the command
    pub fn execute(&self) -> Result<(), Error> {
        
        Ok(())
    }
}