Allow use of @SendToUser even w/o authenticated user

Before this change, subscribing to a user destination and use of
@SendToUser annotation required an authenticated user.

This change makes it possible to subscribe to a user destination from
WebSocket sessions without an authenticated user. In such cases the
destination is associated with one session only rather than with a
user (and all their sessions).

It is then also possible to send a message to a user destination
via "/user/{sessionId}/.." rather than "/user/{user}/...".

That means @SendToUser works relying on the session id of the input
message, effectively sending a reply to destination private to the
session.

A key use case for this is handling an exception with an
@MessageExceptionHandler method and sending a reply with @SendToUser.

Issue: SPR-11309
This commit is contained in:
Rossen Stoyanchev
2014-05-08 19:18:53 -04:00
parent 3c7bb9c279
commit 97fb308b6b
13 changed files with 229 additions and 70 deletions

View File

@@ -332,6 +332,26 @@ public class SendToMethodReturnValueHandlerTests {
verifyNoMoreInteractions(messagingTemplate);
}
@Test
public void sendToUserSessionWithoutUserName() throws Exception {
when(this.messageChannel.send(any(Message.class))).thenReturn(true);
String sessionId = "sess1";
Message<?> inputMessage = createInputMessage(sessionId, "sub1", null, null, null);
this.handler.handleReturnValue(PAYLOAD, this.sendToUserReturnType, inputMessage);
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(this.messageCaptor.getAllValues().get(0));
assertEquals("/user/sess1/dest1", headers.getDestination());
assertEquals("sess1", headers.getSessionId());
headers = SimpMessageHeaderAccessor.wrap(this.messageCaptor.getAllValues().get(1));
assertEquals("/user/sess1/dest2", headers.getDestination());
assertEquals("sess1", headers.getSessionId());
}
private Message<?> createInputMessage(String sessId, String subsId, String destinationPrefix,
String destination, Principal principal) {

View File

@@ -29,7 +29,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Unit tests for {@link org.springframework.messaging.simp.user.DefaultUserDestinationResolver}.
* Unit tests for
* {@link org.springframework.messaging.simp.user.DefaultUserDestinationResolver}.
*
* @author Rossen Stoyanchev
*/
@@ -81,6 +82,19 @@ public class DefaultUserDestinationResolverTests {
assertEquals("/queue/foo-user123", actual.getTargetDestinations().iterator().next());
}
@Test
public void handleSubscribeNoUser() {
String sourceDestination = "/user/queue/foo";
Message<?> message = createMessage(SimpMessageType.SUBSCRIBE, null, SESSION_ID, sourceDestination);
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(sourceDestination, actual.getSourceDestination());
assertEquals(1, actual.getTargetDestinations().size());
assertEquals("/queue/foo-user" + SESSION_ID, actual.getTargetDestinations().iterator().next());
assertEquals(sourceDestination, actual.getSubscribeDestination());
assertNull(actual.getUser());
}
@Test
public void handleUnsubscribe() {
Message<?> message = createMessage(SimpMessageType.UNSUBSCRIBE, this.user, SESSION_ID, "/user/queue/foo");
@@ -116,6 +130,19 @@ public class DefaultUserDestinationResolverTests {
assertEquals("/queue/foo-useropenid123", actual.getTargetDestinations().iterator().next());
}
@Test
public void handleMessageWithNoUser() {
String sourceDestination = "/user/" + SESSION_ID + "/queue/foo";
Message<?> message = createMessage(SimpMessageType.MESSAGE, null, SESSION_ID, sourceDestination);
UserDestinationResult actual = this.resolver.resolveDestination(message);
assertEquals(sourceDestination, actual.getSourceDestination());
assertEquals(1, actual.getTargetDestinations().size());
assertEquals("/queue/foo-user123", actual.getTargetDestinations().iterator().next());
assertEquals("/user/queue/foo", actual.getSubscribeDestination());
assertNull(actual.getUser());
}
@Test
public void ignoreMessage() {
@@ -129,11 +156,6 @@ public class DefaultUserDestinationResolverTests {
actual = this.resolver.resolveDestination(message);
assertNull(actual);
// subscribe + no user
message = createMessage(SimpMessageType.SUBSCRIBE, null, SESSION_ID, "/user/queue/foo");
actual = this.resolver.resolveDestination(message);
assertNull(actual);
// subscribe + not a user destination
message = createMessage(SimpMessageType.SUBSCRIBE, this.user, SESSION_ID, "/queue/foo");
actual = this.resolver.resolveDestination(message);

View File

@@ -108,10 +108,6 @@ public class UserDestinationMessageHandlerTests {
this.messageHandler.handleMessage(createMessage(SimpMessageType.MESSAGE, "joe", "123", "/queue/foo"));
Mockito.verifyZeroInteractions(this.brokerChannel);
// subscribe + no user
this.messageHandler.handleMessage(createMessage(SimpMessageType.SUBSCRIBE, null, "123", "/user/queue/foo"));
Mockito.verifyZeroInteractions(this.brokerChannel);
// subscribe + not a user destination
this.messageHandler.handleMessage(createMessage(SimpMessageType.SUBSCRIBE, "joe", "123", "/queue/foo"));
Mockito.verifyZeroInteractions(this.brokerChannel);