I am developing a spring cloud project with Feign and OAuth2.In the project, there are some time-consuming operations and some requests will be sent when these operations is finished. In order to achieve a better user experience, these operations was moved into an asynchronous method(with @Async). But there arise a problem. I added the OAuth2FeignRequestInterceptor as a bean and has make sure that the Feign Client can work properly in the synchronous method(which thread has correct RequestAttributes in RequestContextHolder).

@Configuration
public class SomeConfiguration{
@Bean
    public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, BaseOAuth2ProtectedResourceDetails resource){
        return new OAuth2FeignRequestInterceptor(oAuth2ClientContext,resource);
    }
}

But if I move these operations into an asynchronous method, there will be throws an exception that scopedTarget.oauth2ClientContext cannot be created due to the absent of RequestContext. I have searched the stackoverflow.com and found a solution: https://stackoverflow.com/questions/23732089/how-to-enable-request-scope-in-async-task-executor/33337838#33337838 With a RequestContextListener bean and these code, the RequestContextHolder belongs to the child thread would be filled with the parent thread's (the request thread) RequestAttributes. Because the asynchronous method will cost some time before invoke the feign client, the request will be reponded before the feign client be invoked. When the request be responded, the RequestContextListener will reset the RequestAttributes in RequestContextHolder by invoke RequestContextHolder.resetRequestAttributes();(RequestContextListener.java:76) and make the request inside the RequestAttributes inactive. When it finished the time-consuming tasks and try to send something by feign client, the feign client try to get the oAuth2ClientContext from the request and throws the exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: Cannot ask for request attribute - request is not active anymore!

I'm not sure if it is appropriate for OAuth2FeignRequestInterceptor to retrieve the Authorization information from the RequestContext in an asynchronous scenario.

Thank you for reading my issue and hoping for your reply.

Comment From: ryanjbaxter

I dont think it was intended to be used like that.

Comment From: jerry-yuan

I dont think it was intended to be used like that.

But if it was not intended to be used like this, could you tell me how can I invoke an OAuth2 protected interface in an asynchronous method by the Feign clients? Or the right approch to notify another microsercvice asynchronously is using a message queue?

Comment From: ryanjbaxter

I dont think we have a solution for it. You might check to see if OpenFeign in general supports it, if so then we can come back and look at that from a Spring Cloud perspective.

Comment From: JohnNiang

There has a solution(#510), but @ryanjbaxter doesn't understand this way : (

I hope the PR will inspire you @jerry-yuan

Comment From: jerry-yuan

There has a solution(#510), but @ryanjbaxter doesn't understand this way : (

I hope the PR will inspire you @jerry-yuan

Thanks for your reply. Actually there are some misunderstanding when I use the feign client in async method. In the original design of feign client, if there is a request context, the sub-requests' authentication will provided by the request from user or upper level service. But if invoked by an async method, there should be an authentication information that represents this service instead of the guy started the async method. I haven't read your changes in detail yet, but it seems like just added a RequestContext to fill the gaps where there is no authentication information representing the service.(maybe it will be a good idea)

Comment From: JohnNiang

There has a solution(#510), but @ryanjbaxter doesn't understand this way : ( I hope the PR will inspire you @jerry-yuan

Thanks for your reply. Actually there are some misunderstanding when I use the feign client in async method. In the original design of feign client, if there is a request context, the sub-requests' authentication will provided by the request from user or upper level service. But if invoked by an async method, there should be an authentication information that represents this service instead of the guy started the async method. I haven't read your changes in detail yet, but it seems like just added a RequestContext to fill the gaps where there is no authentication information representing the service.(maybe it will be a good idea)

If you want to proxy authentication information, you could implement your own RequestInterceptor

@Slf4j
public class KnownHeaderProxyReqInterceptor implements RequestInterceptor {

  private final String[] knownHeaderNames = new String[] {
        "Authorization", "AnyOtherHeader"
  };

  @Override
  public void apply(RequestTemplate template) {
    var requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    log.debug("Trying to proxy known headers for feign invocation. {}",
        Thread.currentThread().getName());
    if (requestAttributes != null) {
      final var request = requestAttributes.getRequest();
      for (String headerName : knownHeaderNames) {
        var headerValue = request.getHeader(headerName);
        if (headerValue != null) {
          log.debug("Feign proxy header name: {}, header value: {}", headerName, headerValue);
          // clear header
          template.header(headerName);
          // set header
          template.header(headerName, headerValue);
        }
      }
    } else {
      log.warn(
          "Empty request attributes detected, and without any headers could be passed by the "
              + "proxy.");
    }
  }
}