【问题标题】:Spring Security Child Thread ContextSpring Security 子线程上下文
【发布时间】:2009-09-04 23:37:19
【问题描述】:

所以我正在使用 Spring Security 开发这个 Spring MVC 应用程序。在某些情况下,我的控制器响应时间过长,我遇到了性能问题。 这是由于一种处理方法可以根据一些用户输入来处理大量数据。

现在,我一直在与我的团队一起在该处理方法中或围绕该处理方法稍微调整代码,我认为如果不对其进行切片并异步执行每个切片,我们无法从中获得更好的性能。

问题是当我尝试使用 java.util.concurrent 中的线程池对其进行切片并将工作分配给子线程时,我在执行时收到有关安全上下文的错误消息。

这是堆栈跟踪的摘录:

Exception in thread "pool-1-thread-3" org.springframework.security.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:342)
    at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:254)
    at org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:63)
Exception in thread "pool-1-thread-4"   at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at $Proxy63.batchSaveCampaignpayment(Unknown Source)
    at com.fim.pnp.controller.PaymentForm$1.run(PaymentForm.java:224)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)
    at java.lang.Thread.run(Thread.java:595)
org.springframework.security.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:342)
    at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:254)
    at org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:63)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at $Proxy63.batchSaveCampaignPayment(Unknown Source)
    at com.fim.pnp.controller.PaymentForm$1.run(PaymentForm.java:224)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)
    at java.lang.Thread.run(Thread.java:595)
Exception in thread "pool-1-thread-5" org.springframework.security.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
    at org.springframework.security.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:342)
    at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:254)
    at org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:63)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at $Proxy63.batchSaveCampaignPayment(Unknown Source)
    at com.fim.pnp.controller.PaymentForm$1.run(PaymentForm.java:224)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)
    at java.lang.Thread.run(Thread.java:595)

我知道从请求中生成线程不是一个好习惯……但我们目前已经没有什么想法了,根据我们的测量,每个工作线程所花费的时间不应超过几秒钟。此外,预计此功能将仅由 1 或 2 个专用用户每周使用一次。

有没有办法将 securityContext 传递给子线程或允许这些线程执行的任何类似方法?

谢谢

【问题讨论】:

  • 假设您使用的是TaskExecutor,您可以将其包装在DelegatingSecurityContextTaskExecutor 中,它会处理所有这些问题。所有这些都在reference guide中进行了解释。

标签: java multithreading spring-security


【解决方案1】:

遇到类似问题时,我找到了两种解决方案:

解决方案 1 将 SecurityContextHolder 的策略更改为 MODE_INHERITABLETHREADLOCAL

你可以这样做

<beans:bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <beans:property name="targetClass"
              value="org.springframework.security.core.context.SecurityContextHolder"/>
    <beans:property name="targetMethod" value="setStrategyName"/>
    <beans:property name="arguments" value="MODE_INHERITABLETHREADLOCAL"/>
</beans:bean>

解决方案 2 使用 DelegatingSecurityContextRunnableDelegatingSecurityContextExecutorDelegatingSecurityContextExecutor。这个解决方案在Spring Concurrency Support Documentation

中有很好的描述

【讨论】:

【解决方案2】:

我们通常会执行以下操作: 在最初的 spring 管理的线程中做

Locale locale = LocaleContextHolder.getLocale();
RequestAttributes ra = RequestContextHolder.getRequestAttributes();

现在您需要将这两个值放在您的新线程可以找到它们的地方。然后你做:

 LocaleContextHolder.setLocale( locale, true);
 RequestContextHolder.setRequestAttributes( ra, true);

在您的新线程中。虽然我不确定这是否是受支持的方法,但它总是运行良好。

【讨论】:

  • 我已经尝试过该代码。将 2 个变量设为最终变量,以便我可以将它们与 Threadpool 一起使用。仍然有同样的错误。我已经通过打印它们正确传递给子线程的变量的内容来确保。
【解决方案3】:

也许您可以使用类似 InheritableThreadLocalSecurityContextHolderStrategy 的东西?我认为它的作用是将当前线程的安全上下文复制到您在其中创建的任何线程

【讨论】:

    【解决方案4】:

    正如其他提到的,在documentation 中有一个简洁的示例。这可以通过使用 lambda 函数来改进。

    SecurityContext securityContext = SecurityContextHolder.getContext();
    DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(() -> doSomething()), securityContext);
    new Thread(wrappedRunnable).start();
    

    不过,我还想指出另一个解决方案。我建议使用 @Async 注释。您只需要添加以下配置。

    @Bean("contextPropagatedExecutor")
    public TaskExecutor contextPropagatedExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(15);
        executor.setQueueCapacity(Integer.MAX_VALUE);
        executor.setThreadNamePrefix("PropagatedContext-");
        executor.initialize();
        return new DelegatingSecurityContextAsyncTaskExecutor(executor);
    }
    

    然后,您需要做的就是添加如下注释。这更易于管理,并且可以在需要时重复使用。

    @Async("contextPropagatedExecutor")
    public void doSomething(){}
    

    【讨论】:

      猜你喜欢
      • 2018-06-26
      • 2019-03-01
      • 2022-01-25
      • 2012-09-24
      • 2013-01-20
      • 2014-09-15
      • 2015-03-29
      • 2016-09-27
      • 1970-01-01
      相关资源
      最近更新 更多