I am working on a Spring-MVC application in which we are using Spring-security for authentication and authorization. There are some tasks where we would like to use a Thread, but whenever we are using a Java thread, and try to obtain details about authenticated user.
I also tried the @Async
annotation, but it was not what we are looking as only specific parts of the code we need in a thread.
Is there any way to inject spring-session in a Java thread? If not, any alternative ways to run parts of code in a separate thread. Thank you.
Example code :
public void sendNotification(Notification notification, int memberId) {
Thread thread = new Thread(() -> {
Person onlinePerson = this.personService.getCurrentlyAuthenticatedUser();
GroupMembers groupMembers = this.groupMembersService.getMemberById(memberId);
}
thread.start();
}
Now, in the code above, if I try to get any information about onlinePerson, such as id with onlinePerson.getId();, I get an NPE. Check out the error log :
Exception in thread "Thread-9" java.lang.NullPointerException
at com.project.spring.chat.ChatServiceImpl.lambda$sendNotification$3(ChatServiceImpl.java:532)
at java.lang.Thread.run(Thread.java:745)
Any idea how I can tackle this problem. Thank you.
Security-applicationContext.xml :
<security:http pattern="/resources/**" security="none"/>
<security:http create-session="ifRequired" use-expressions="true" auto-config="false" disable-url-rewriting="true">
<security:form-login login-page="/login" username-parameter="j_username" password-parameter="j_password"
login-processing-url="/j_spring_security_check" default-target-url="/canvaslisting"
always-use-default-target="true" authentication-failure-url="/denied"/>
<security:remember-me key="_spring_security_remember_me" user-service-ref="userDetailsService"
token-validity-seconds="1209600" data-source-ref="dataSource"/>
<security:logout delete-cookies="JSESSIONID" invalidate-session="true" logout-url="/j_spring_security_logout"/>
<!--<security:intercept-url pattern="/**" requires-channel="https"/>-->
<security:port-mappings>
<security:port-mapping http="80" https="443"/>
</security:port-mappings>
<security:logout logout-url="/logout" logout-success-url="/" success-handler-ref="myLogoutHandler"/>
<security:session-management session-fixation-protection="newSession">
<security:concurrency-control session-registry-ref="sessionReg" max-sessions="5" expired-url="/login"/>
</security:session-management>
</security:http>
<beans:bean id="sessionReg" class="org.springframework.security.core.session.SessionRegistryImpl"/>
<beans:bean id="rememberMeAuthenticationProvider"
class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
<beans:constructor-arg index="0" value="_spring_security_remember_me"/>
<beans:constructor-arg index="1" ref="userDetailsService"/>
<beans:constructor-arg index="2" ref="jdbcTokenRepository"/>
<property name="alwaysRemember" value="true"/>
</beans:bean>
<beans:bean id="jdbcTokenRepository"
class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
<beans:property name="createTableOnStartup" value="false"/>
<beans:property name="dataSource" ref="dataSource"/>
</beans:bean>
<!-- Remember me ends here -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="LoginServiceImpl">
<security:password-encoder ref="encoder"/>
</security:authentication-provider>
</security:authentication-manager>
<beans:bean id="encoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg name="strength" value="11"/>
</beans:bean>
<beans:bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="LoginServiceImpl"/>
<beans:property name="passwordEncoder" ref="encoder"/>
</beans:bean>
</beans>
This is because the session information is stored in a ThreadLocal
against the thread that handled the HTTP request. It's not by default available to new threads (whether you create them manually or via @Async
).
Fortunately this is easy to configure:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL) //in a context loader listener for example
Or with the environment variable:
spring.security.strategy=MODE_INHERITABLETHREADLOCAL
Your thread class is not managed by spring, because in your code example you manually create the class instance by calling its constructor. You need to get your thread instance managed by spring and retrieve it from a bean using spring's ApplicationContext
. I guess you want multiple thread instances running at the same time? If so, you may need a new thread-instance (bean) for each thread execution. Here is an example of how you can achieve this:
@Component
@Scope("prototype") // the scope “prototype" will return a new instance each time when the bean will be retrieved.
public class YourThreadClass implements Runnable {
private String anyString;
@Autowired
AnyOtherBean anyOtherBean; // since YourThreadClass is now managed by spring, you can also inject services / components into YourThreadClass
}
@Component
public class YourClassThatStartsTheThread{
@Autowired
ApplicationContext ctx;
public void sendNotification(Notification notification, int memberId){
// ... your code ....
YourThreadClass aNewInstanceOfYourThreadClass = (YourThreadClass) ctx.getBean(YourThreadClass.class);
// ... your code ...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With