6

所以我正在使用 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 传递给子线程或允许这些线程执行的任何类似方法?

谢谢

4

4 回答 4

3

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

解决方案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中有很好的描述

于 2017-06-07T13:37:43.133 回答
2

我们通常会做以下事情: 在最初的 spring-managed 线程中做

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

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

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

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

于 2009-09-06T07:05:44.287 回答
1

Maybe you could use something like InheritableThreadLocalSecurityContextHolderStrategy ? I think what it does is to copy the security context of the current thread to any threads you create within

于 2009-09-06T21:12:20.393 回答
1

正如其他提到的,在文档中有一个简洁的示例。这可以通过使用 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(){}
于 2021-09-02T20:07:36.880 回答