Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring-security session not available in java thread

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>
like image 741
We are Borg Avatar asked Sep 11 '25 17:09

We are Borg


2 Answers

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

like image 132
StuPointerException Avatar answered Sep 13 '25 06:09

StuPointerException


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 ...
   }
}
like image 31
mika Avatar answered Sep 13 '25 06:09

mika