【问题标题】:Using OAuth2RestTemplate in multi-thread context在多线程上下文中使用 OAuth2RestTemplate
【发布时间】:2016-07-07 01:50:37
【问题描述】:

我在我的 Spring 启动应用程序中使用 OAuth2RestTemplate 并通过它使用一些资源,因为它封装了所有身份验证信息,所以我可以发送请求,而不用担心令牌和其他身份验证内容。

在我并行发送请求之前一切正常。

由于 OAuth2RestTemplate 有一个Session 范围(它是本地的,因为它包含用户的会话相关信息),当我尝试在多线程环境中使用它时,我得到以下异常

org.springframework.beans.factory.BeanCreationException:创建名为“scopedTarget.oauth2ClientContext”的bean时出错:范围 当前线程的“会话”不活动; 如果您打算从单例中引用它,请考虑为该 bean 定义一个作用域代理; 嵌套异常是 java.lang.IllegalStateException: No thread-bound request found: 你指的是请求属性吗 在实际的网络请求之外, 或处理原始接收线程之外的请求?如果您实际上是在 Web 请求中操作并且仍然 收到这条消息, 您的代码可能在 DispatcherServlet/DispatcherPortlet 之外运行:在这种情况下,请使用 RequestContextListener 或 RequestContextFilter 暴露当前 请求。

据我了解,发生这种情况是因为执行代码的这些单独线程没有与会话连接。

我目前找到的唯一解决方案是在代码中手动将会话与新线程绑定,但我不喜欢它。

    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

    Stream.of(1, 2, 3).parallel().forEach(it -> {
        RequestContextHolder.setRequestAttributes(requestAttributes);
        //do something
        RequestContextHolder.resetRequestAttributes();
    });

    RequestContextHolder.setRequestAttributes(requestAttributes);

有一个ticket on Spring Jira 讨论了类似的问题,但我仍然希望有一些与 OAuth2RestTemplate 相关的解决方案。

所以我想知道是否有人遇到过这个问题以及你是如何解决的。

【问题讨论】:

    标签: multithreading spring-security spring-boot spring-security-oauth2 resttemplate


    【解决方案1】:

    在我创建自己的 bean 并确保它被正确自动装配后,我能够轻松地在多个线程中使用 OAuth2RestTemplate

    在我收到完全相同的错误消息之前,我发现我没有收到我之前创建的OAuth2RestTemplate

    @Bean
    @Autowired
    public OAuth2RestTemplate oAuth2RestTemplate(
        OAuth2ProtectedResourceDetails resourceDetails,
        SpringClientFactory clientFactory) {
    
        OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails);
        restTemplate.setRequestFactory(new RibbonClientHttpRequestFactory(clientFactory));
    
        return restTemplate;
    }
    

    特别是,自动连接的RestTemplate 中不存在请求工厂,实际上它是null。此外,我实际上有多个不同的OAuth2RestTemplate bean,但@Autowired 为所有这些bean 返回了完全相同的实例。

    钥匙竟然在OAuth2RestOperationsConfiguration

    @Bean
    @Primary
    public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
            OAuth2ProtectedResourceDetails details) {
        OAuth2RestTemplate template = new OAuth2RestTemplate(details,
                oauth2ClientContext);
        return template;
    }
    

    由于@Primary 注释,所有@Autowired 实例显然都会得到这个。通过参数名称指定要获取的 bean,以前通常可以使用,但在这种情况下却没有。

    添加@Qualifier 注释解决了这个问题:

    @Autowired
    @Qualifier("myOwnOAuth2RestTemplate")
    OAuth2RestTemplate oAuth2RestTemplate;
    

    现在,返回正确的实例,并在发出请求时调用身份验证服务器以获取 OAuth2 令牌,如security.oauth2.client 中的应用程序配置中配置的那样。

    但是,我没有尝试从新线程中自动连接 RestTemplate。相反,我将其作为参数传入。我不完全确定这是否会有所不同,但由于getAccessToken() 在请求执行期间由RestTemplate 调用,我认为它在自动连接到新线程时也应该工作。

    【讨论】:

    • 你已经制作了 bean 单例,所以它可以正常工作,因为 Spring 可以处理将单例连接到不同的线程。但这里的问题是 oAuth2RestTemplate 默认有一个会话范围来存储用户的相关身份验证信息,因此每个用户都可以使用它自己的 bean。我不确定这是否可以与 Singleton oAuth2RestTemplate 一起正常工作。
    • @ikryvorotenko 鉴于您想在SecurityContext 中使用经过身份验证的主体的 OAuth2 访问令牌,实际上您不需要为每个用户使用单独的 bean。您可以使用来自SecurityContext 中的Authentication 的令牌更新OAuth2ClientContext。而且我认为最终由于 Spring 的代理魔法,会话范围的 bean 不是“真正的”单例。事实上,我们根据我的回答中描述的方法成功地使用了不同的 OAuth2RestTemplate bean,以及使用静态用户名/密码凭据获取令牌的其他 bean。
    【解决方案2】:

    为了使用会话或请求范围,您需要在 @Configuration 类中将 RequestContextListener 公开为 bean。例如,请参阅this thread

    【讨论】:

    • 那行不通-至少对我来说-即使我尝试在春天禁用已经实例化的RequestContextFilter,并使用threadContextInheritable创建我自己的true,但它只是没有工作。在这里找到了建议的解决方案,它就像一个魅力:medium.com/@pranav_maniar/…
    猜你喜欢
    • 1970-01-01
    • 2018-12-31
    • 1970-01-01
    • 1970-01-01
    • 2020-10-03
    • 1970-01-01
    • 2020-07-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多