经OAuth2身份验证的伪装客户端不支持以异步方法调用?

时间:2019-07-02 07:03:58

标签: spring-cloud-feign spring-cloud-security

我正在用Feign和OAuth2开发一个Spring Cloud项目,该项目中有一些耗时的操作,这些操作完成后会发送一些请求。为了获得更好的用户体验,这些操作已移至异步方法(使用@Async)中。但是出现一个问题。 我添加了OAuth2FeignRequestInterceptor作为bean,并确保Feign Client可以在同步方法中正常工作(该线程在RequestContextHolder中具有正确的RequestAttributes)。

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

但是,如果我将这些操作移到异步方法中,则会引发异常,由于RequestContext的缺失,无法创建scopedTarget.oauth2ClientContext。我搜索了stackoverflow.com并找到了解决方案: How to enable request scope in async task executor 使用RequestContextListener bean和这些代码,属于子线程的RequestContextHolder将被父线程(请求线程)的RequestAttributes填充。 因为异步方法在调用伪客户端之前会花费一些时间,所以该请求将在伪客户端被调用之前得到响应。响应请求后,RequestContextListener将通过调用RequestContextHolder.resetRequestAttributes();(RequestContextListener.java:76)重置RequestContextHolder中的RequestAttributes,并使RequestAttributes内部的请求处于非活动状态。当完成耗时的任务并尝试通过伪装客户端发送某些内容时,伪装客户端尝试从请求中获取oAuth2ClientContext并引发异常:

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!

我不确定OAuth2FeignRequestInterceptor在异步情况下从RequestContext检索授权信息是否合适。

感谢您阅读我的问题并希望得到您的答复。

1 个答案:

答案 0 :(得分:0)

如果使用spring,则可以将spring安全上下文绑定到子线程。

SecurityContext context = SecurityContextHolder.getContext();
ExecutorService delegateExecutor = new ExecutorServiceAdapter(this.taskExecutor);
DelegatingSecurityContextExecutorService executor = new DelegatingSecurityContextExecutorService(delegateExecutor, context);
executor.invokeAll(tasks).stream()...

需要定义taskExecutor bean:

@Configuration
public class ThreadConfig {
@Bean
public TaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(4);
    executor.setMaxPoolSize(10);
    executor.setThreadNamePrefix("task_thread");
    executor.initialize();
    return executor;
}
}

最后,最重要的是,在servlet启动时需要启用setThreadContextInheritable:

DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThreadContextInheritable(true);