Reputation: 423
I have a webapp that receives messages from a separate application. When the webapp receives the message it will send the message to client users. Essentially I have a DefaultMessageListenerContainer that listens for incoming messages. The container uses a MessageListenerAdapter. When MessageDelagate receives a message from the separate application, the messageDelegate implementation sends this message to the principal via a queue using a SimpMessageSendingOperations. To access the Principal I am using the SecurityContextHolder to get the authentication object when the user logged in. However, I am getting a null authentication object(see system.out in my code below). When
<bean id="messageDelegate" class="com.mypackage.MyMessageDelegate"></bean>
<bean id="messageListener" class="...">
<constructor-arg ref="messageDelegate" />
</bean>
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="..."/>
<property name="destination" ref="..."/>
<property name="messageListner" ref="messageListener"/>
</bean>
@Component
public class MyMessageDelegate implements MessageDelegate{
@Autowired
private SimpMessageSendingOperations messageTemplate;
@Override
public void handleMessage(Serializable message){
System.out.println(SecurityContextHolder.getContext().getAuthentication());
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
messageTemplate.convertAndSendToUser(user.getUsername(),"/queue/updateToUser",message);
}
}
TO ensure that i have an authenticated user I created Controller below and a bean to get the authentication. In the case below TestImpl is successfully getting the authentication.
@Component
public class TestImpl {
public void logAuthentication(){
System.out.println("------------aUTH----------------");
System.out.println(SecurityContextHolder.getContext().getAuthentication());
}
}
@Controller
public class MyController{
@AutoWired
private TestImpl testImpl;
public String go(){
testImpl.logAuthentication();
}
}
So my theory is that the Authentication is not passed to the jmsCotainer. Is this the case here? If so is there any way that MyMessageDelegate get the authentication and how?
Upvotes: 0
Views: 1505
Reputation: 20135
In a default setup, Spring Security stores the authentication token in the HTTP session upon successful authentication. Then, before handing over each HTTP request to the respective controller, the authentication token is copied from the HTTP session to the current thread's ThreadLocal
. In a normal Spring MVC application each HTTP request is handled on a single thread. Therefore, calling SecurityContextHolder.getContext().getAuthentication()
any time during the course of a single HTTP call gives the right authentication token to the caller since the token is on the ThreadLocal
.
JMS message handlers on the other hand are asynchronous. Most JMS implementations reserve a separate thread pool for running JMS message handlers. Therefore, handing off a JMS message from within a Spring MVC call to a JMS handler may (important to note may
instead of will
) cross thread boundaries. For all practical purposes, developers must not assume that the authentication token retrieved in the Spring MVC call chain can also be safely retrieved within a JMS message handler invoked as a result of a message from the same MVC call chain.
The solution for you would be to include the authenticated principal in the JMS message and then retrieve rest of the information in the message handler itself. In my opinion, hard-coding SecurityContextHolder.getContext().getAuthentication()
throughout your code is anyways not a good idea because some day you may want to move away from Spring Security (who knows a better option becomes available in the future) and you would not want to hunt through your code base for direct calls to the Spring Security infrastructure. This makes it even more important to exchange just the principal in the JMS message instead of the complete authentication token.
Upvotes: 1