While updating settings I want to run a thread that updates some extra things in the background (for which the user shouldn't wait). It used to work with Spring4 but cannot make it work in Spring5 (JDK17). I know I'm probably missing something silly I just cannot see it ...
Basically I want to call this as Async:
tenantUpdateHandler.update(tenantId, settings);
The call is wrapped in a Runnable class and started like so:
UpdateTenantThread updateTenantThread = applicationCtx.getBean(UpdateTenantThread.class);
updateTenantThread.init(tenantId, libraryId, settings);
Thread updateThread = new Thread(updateTenantThread);
updateThread.start();
The exception I get after starting the thread:
Exception in thread "Thread-27" org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:350)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:214)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at com.app.saas.service.TenantService$$EnhancerBySpringCGLIB$$9d387e3f.update(<generated>)
at com.app.saas.service.TenantUpdateHandlerImpl.update(TenantUpdateHandlerImpl.java:44)
at com.app.core.service.UpdateTenantThread.run(UpdateTenantThread.java:47)
at java.base/java.lang.Thread.run(Thread.java:833)
The TenantService from the stack is secured (@Service, @Secured({Role.ADMIN}))
I have enabled thread inheritance for Spring Security like so:
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.springframework.security.core.context.SecurityContextHolder"/>
<property name="targetMethod" value="setStrategyName"/>
<property name="arguments">
<list>
<value>MODE_INHERITABLETHREADLOCAL</value>
</list>
</property>
</bean>
This actually used to be just a private subclass, but I changed it to a Component to make sure it is managed by Spring.
/**
* Update tenant data for Thread.
*/
@Component
@Scope("prototype")
public class UpdateTenantThread implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(UpdateTenantThread.class);
Long tenantId;
Properties settings;
@Autowired
private TenantUpdateHandler tenantUpdateHandler;
UpdateTenantCopy() {}
public void init(Long tenantId,
Properties settings) {
this.tenantId = tenantId;
this.settings = (Properties) settings.clone();
}
@Override
public void run() {
try {
tenantUpdateHandler.update(tenantId, settings);
} catch (TenantServiceException ex) {
LOG.error("Problem updating copy of settings", ex);
}
}
}