创建名称为“ scopedTarget.oauth2ClientContext”的bean时出错:伪客户端的当前线程的作用域“请求”未激活

时间:2019-01-11 10:02:04

标签: spring-boot oauth-2.0 scope jhipster feign

在我当前使用Jhipster构建的微服务中,当我当前的微服务启动并准备好使用伪装客户端时,我将调用另一个微服务。 所以我的假冒接口是

package com.persistent.integration.client;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.persistent.integration.service.dto.DataPipelineDTO;

@AuthorizedFeignClient(name = "Integrationconfiguration") 
public interface DataPipelinesResourceFeign {
     @RequestMapping(value = "/api/data-pipelines", method = RequestMethod.GET)     
     List<DataPipelineDTO> getAllDataPipelines(@RequestParam(value = "pageable") Pageable pageable );       
}

}

我已经实现了ApplicationRunner,在这里我称之为伪客户端方法。

@Component
public class ApplicationInitializer implements ApplicationRunner {

    @Autowired
    private DataPipelinesResourceFeign dataPipelinesResourceFeign; 

    @Autowired
    private ActiveMQListener activeMqListener;


    @Override
    public void run(ApplicationArguments args) throws Exception {
        // TODO Auto-generated method stub
        Pageable pageable = PageRequest.of(0, 20);
        try {
        List <DataPipelineDTO> allStartedDataPipeLines = dataPipelinesResourceFeign.getAllDataPipelines(pageable);      //.stream().filter(p->p.getState().equals(State.STARTED)).collect(Collectors.toList());

        allStartedDataPipeLines.forEach(datapipe -> 
                {
                    try {
                        activeMqListener.consume(datapipe);
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                });
        } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
        }
    }

但是运行此命令后,它将在dataPipelinesResourceFeign.getAllDataPipelines处给出以下异常:

com.netflix.hystrix.exception.HystrixRuntimeException: DataPipelinesResourceFeign#getAllDataPipelines(Pageable) failed and no fallback available.
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
    at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
    at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
    at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
    at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1472)
  

原因:org.springframework.beans.factory.BeanCreationException:   创建名称为“ scopedTarget.oauth2ClientContext”的bean时出错:   范围“请求”对于当前线程无效。考虑   如果要引用它,则为此bean定义作用域代理   从单身嵌套的异常是java.lang.IllegalStateException:   找不到线程绑定的请求:您是否在引用请求属性   在实际的Web请求之外,或在外部处理请求   最初的接收线程?如果您实际在   一个网络请求,但仍然收到此消息,您的代码可能是   在DispatcherServlet / DispatcherPortlet外部运行:在这种情况下,   使用RequestContextListener或RequestContextFilter公开   当前请求。在

     
    

org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac> tBeanFactory.java:362)       在     org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractB> eanFactory.java:199)       在     org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTarge> tSource.java:35)       在     org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy。> java:193)       在com.sun.proxy。$ Proxy147.getAccessToken(Unknown Source)处     com.persistent.integration.security.oauth2.AuthorizationHeaderUtil.getAuthoriza> tionHeaderFromOAuth2Context(AuthorizationHeaderUtil.java:28)       在     com.persistent.integration.client.TokenRelayRequestInterceptor.apply(TokenRelay> RequestInterceptor.java:23)       在     feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:158)       在     feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88)       在     feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)       在     feign.hystrix.HystrixInvocationHandler $ 1.run(HystrixInvocationHandler.java:108)       在com.netflix.hystrix.HystrixCommand $ 2.call(HystrixCommand.java:302)       在com.netflix.hystrix.HystrixCommand $ 2.call(HystrixCommand.java:298)       在     rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)       ... 68更多原因:java.lang.IllegalStateException:否     找到线程绑定的请求:您是否在引用请求属性     在实际的Web请求之外,或在外部处理请求     最初的接收线程?如果您实际在     一个网络请求,但仍然收到此消息,您的代码可能是     在DispatcherServlet / DispatcherPortlet外部运行:在这种情况下,     使用RequestContextListener或RequestContextFilter公开     当前请求。在     org.springframework.web.context.request.RequestContextHolder.currentRequestAttr> ibutes(RequestContextHolder.java:131)       在     org.springframework.web.context.request.AbstractRequestAttributesScope.get(Abst> ractRequestAttributesScope.java:42)       在     org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac> tBeanFactory.java:350)

  

Internet上的许多建议是添加侦听器RequestContextListener。但是,即使我在onStartup方法的webConfigurer.java中添加了侦听器,问题仍然存在。

{

servletContext.addListener(RequestContextListener.class);

} 但是没有用。

任何线索都将不胜感激。

2 个答案:

答案 0 :(得分:0)

我找到了解决方法。我不知道为什么TokenRelayRequestIntercepton无法正常工作,但是您可以基于Spring的SecurityContext使用自己的RequestInterceptor。

首先,定义一个RequestInterceptor:

    public class MyRequestInterceptor implements RequestInterceptor {

    public static final String AUTHORIZATION = "Authorization";
    public static final String BEARER = "Bearer";

    public MyRequestInterceptor() {
        super();
    }

    @Override
    public void apply(RequestTemplate template) {
        // demander un token à keycloak et le joindre à la request
        Optional<String> header = getAuthorizationHeader();
        if (header.isPresent()) {
            template.header(AUTHORIZATION, header.get());
        }
    }

    public static Optional<String> getAuthorizationHeader() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication  != null && authentication.getDetails() != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
            OAuth2AuthenticationDetails oAuth2AuthenticationDetails =
                (OAuth2AuthenticationDetails) authentication.getDetails();

            return Optional.of(String.format("%s %s", oAuth2AuthenticationDetails.getTokenType(),
                oAuth2AuthenticationDetails.getTokenValue()));

        } else {
            return Optional.empty();
        }
    }

}

,然后使用RequestInterceptor为假客户端声明一个配置类,该类应包含以下内容:

@Bean(name = "myRequestInterceptor")
public RequestInterceptor getMyRequestInterceptor() throws IOException {
    return new MyRequestInterceptor();
}

您的Feign客户应如下所示:

 @FeignClient(name = "SERVICE_NAME", configuration = MyFeignConfiguration.class)
public interface MyRestClient {

答案 1 :(得分:0)

在使用 ApplicationRunner 在启动时运行Feign Client时,我遇到了同样的问题,并且提出了以下解决方案。

我用 OAuth2FeignRequestInterceptor 定义了我的 FeignClientsConfiguration ,它接受预定义的bean DefaultOAuth2ClientContext 和OAuth2配置 OAuth2ProtectedResourceDetails

@Configuration
public class MyConfig extends FeignClientsConfiguration {

    @Bean
    public RequestInterceptor oauth2FeignRequestInterceptor(                                                           DefaultOAuth2ClientContext oAuth2ClientContext, MyOauth2Properties properties) {
        return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resourceDetails(properties));
    }

    @Bean
    public DefaultOAuth2ClientContext oAuth2ClientContext() {
        return new DefaultOAuth2ClientContext();
    }

    private OAuth2ProtectedResourceDetails resourceDetails(MyOauth2Properties oauth2Properties) {
        ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setAccessTokenUri(oauth2Properties.getAccessTokenUri());
        resourceDetails.setUsername(oauth2Properties.getUsername());
        resourceDetails.setPassword(oauth2Properties.getPassword());
        resourceDetails.setClientId(oauth2Properties.getClientId());
        return resourceDetails;
    }
}

您的假装客户看起来像这样:

@FeignClient(url = "http://localhost:8080/api/v1")
public interface FeignClient {
}

所有这些之后,从 ApplicationRunner.run()调用FeignClient可以正常工作。 Spring Boot 2.2.6