Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security Child Thread Context

So I'm working on this Spring MVC application using Spring Security. I've been hitting a performance problem in some instances where my controller is taking way too long to respond. This is due to a processing method that can take a huge amount of data in to process, based on some user input.

Now I've been tweaking the code a bit in and around that processing method with my team and I don't think we can get much better performance out of that without slicing it and executing each slice asynchronously.

The problem is when I try to slice it and distribute the work to child threads, using a threadpool from java.util.concurrent, I get error messages about the security context when those execute.

Here is an extract of the stacktrace:

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)

I know it's not good practice to spawn threads from a request... but we've ran out of ideas at this point and each worker thread shouldn't take more than a handful of seconds by our measurements. Also it is expected that this feature is going to be used by 1 or 2 dedicated users once a week only.

Is there a way to pass the securityContext to the child threads or anything similar that would allow those threads to execute?

Thanks

like image 258
Lancelot Avatar asked Sep 04 '09 23:09

Lancelot


People also ask

What is security context in Spring Security?

SecurityContextHolder - The SecurityContextHolder is where Spring Security stores the details of who is authenticated. SecurityContext - is obtained from the SecurityContextHolder and contains the Authentication of the currently authenticated user.

Is SecurityContextHolder thread safe?

Yes, it's thread safe with the default strategy ( MODE_THREADLOCAL ) (as long as you don't try to change the strategy on the fly). However, if you want spawned threads to inherit SecurityContext of the parent thread, you should set MODE_INHERITABLETHREADLOCAL .

What is AuthenticationManagerBuilder?

AuthenticationManagerBuilder. parentAuthenticationManager(AuthenticationManager authenticationManager) Allows providing a parent AuthenticationManager that will be tried if this AuthenticationManager was unable to attempt to authenticate the provided Authentication . protected ProviderManager.

What is Mode_inheritablethreadlocal?

Any new thread does not get the context information from the parent thread. MODE_INHERITABLETHREADLOCAL : It copies the security context to the next thread in case of an asynchronous method. MODE_GLOBAL : It makes all the threads of the application see the same security context instance.


4 Answers

I found two solutions when I had a similar problem:

Solution 1 Change strategy of SecurityContextHolder to MODE_INHERITABLETHREADLOCAL

You may do it in such way

<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>

Solution 2 Use DelegatingSecurityContextRunnable, DelegatingSecurityContextExecutor or DelegatingSecurityContextExecutor. This soulution is well described in Spring Concurrency Support Documentation

like image 175
Volodymyr Semaniv Avatar answered Oct 10 '22 04:10

Volodymyr Semaniv


We usually do the following: In the initial spring-managed thread do

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

Now you need to put those two values somewhere your new thread can find them. Then you do:

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

In your new thread. Although I'm not sure if this is the supported method, it's always worked well.

like image 28
krosenvold Avatar answered Oct 10 '22 02:10

krosenvold


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

like image 34
Luciano Avatar answered Oct 10 '22 04:10

Luciano


As other mentioned, in the documentation there is a neat example. Which could be improved by using lambda functions.

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

Nonetheless, I would also like to point to another solution. I would recommend using the @Async annotation. You would just need to add the following configuration.

@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);
}

Then, all you would need to do is to add the annotation like followed. This is easier to manage and can be reutilized if needed.

@Async("contextPropagatedExecutor")
public void doSomething(){}
like image 21
Alain Cruz Avatar answered Oct 10 '22 02:10

Alain Cruz