From 39a746d30f82bf63b2906ecba82303cf311b9fc2 Mon Sep 17 00:00:00 2001 From: cuitianhao <54015884+tianhaocui@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:55:53 +0800 Subject: [PATCH 1/3] Set host header consistently in STOMP relay CONNECT frames StompBrokerRelayMessageHandler only set the host header in CONNECT frames when virtualHost was explicitly configured. Per STOMP 1.2, the host header is required on CONNECT frames. Fall back to relayHost (the TCP connection target) when virtualHost is not configured, ensuring the host header is always present in both system session and client session CONNECT frames. Closes gh-36673 Signed-off-by: cuitianhao <54015884+tianhaocui@users.noreply.github.com> --- .../simp/stomp/StompBrokerRelayMessageHandler.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java index cab92b50ac8..6c5aeb5f17e 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandler.java @@ -456,10 +456,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler accessor.setLogin(this.systemLogin); accessor.setPasscode(this.systemPasscode); accessor.setHeartbeat(this.systemHeartbeatSendInterval, this.systemHeartbeatReceiveInterval); - String virtualHost = getVirtualHost(); - if (virtualHost != null) { - accessor.setHost(virtualHost); - } + accessor.setHost(getVirtualHost() != null ? getVirtualHost() : getRelayHost()); accessor.setSessionId(SYSTEM_SESSION_ID); if (logger.isDebugEnabled()) { logger.debug("Forwarding " + accessor.getShortLogMessage(EMPTY_PAYLOAD)); @@ -582,9 +579,7 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler stompHeaderAccessor = (stompHeaderAccessor.isMutable() ? stompHeaderAccessor : StompHeaderAccessor.wrap(message)); stompHeaderAccessor.setLogin(this.clientLogin); stompHeaderAccessor.setPasscode(this.clientPasscode); - if (getVirtualHost() != null) { - stompHeaderAccessor.setHost(getVirtualHost()); - } + stompHeaderAccessor.setHost(getVirtualHost() != null ? getVirtualHost() : getRelayHost()); RelayConnectionHandler handler = new RelayConnectionHandler(sessionId, stompHeaderAccessor); this.connectionHandlers.put(sessionId, handler); this.stats.incrementConnectCount(); From a9d344b3ed4d5c12f67033fa72fdc0f6945d114d Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 28 Apr 2026 12:10:06 +0100 Subject: [PATCH 2/3] TransportHandlingSockJsService checks remoteAddress Closes gh-36681 --- .../TransportHandlingSockJsService.java | 16 ++++++++++++- .../handler/DefaultSockJsServiceTests.java | 24 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java index 0c98eeb1525..dd44e533cb9 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java @@ -17,6 +17,7 @@ package org.springframework.web.socket.sockjs.transport; import java.io.IOException; +import java.net.InetSocketAddress; import java.security.Principal; import java.time.Duration; import java.util.ArrayList; @@ -308,7 +309,20 @@ public class TransportHandlingSockJsService extends AbstractSockJsService implem Principal currentPrincipal = request.getPrincipal(); if (!principal.equals(currentPrincipal) && (currentPrincipal == null || !principal.getName().equals(currentPrincipal.getName()))) { - logger.debug("The user for the session does not match the user for the request."); + logger.debug("The user for the session and the request do not match¶."); + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + } + else { + if (request.getPrincipal() != null) { + logger.debug("The request has a user, but the session does not."); + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + InetSocketAddress remoteAddress = session.getRemoteAddress(); + if (remoteAddress != null && !remoteAddress.equals(request.getRemoteAddress())) { + logger.debug("The remote address for the session and the request do not match."); response.setStatusCode(HttpStatus.NOT_FOUND); return; } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsServiceTests.java index d7ed52c78d5..70f62314ce6 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsServiceTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsServiceTests.java @@ -16,6 +16,7 @@ package org.springframework.web.socket.sockjs.transport.handler; +import java.net.InetSocketAddress; import java.time.Duration; import java.util.Arrays; import java.util.Collections; @@ -280,6 +281,29 @@ class DefaultSockJsServiceTests extends AbstractHttpRequestTests { verifyNoMoreInteractions(this.xhrSendHandler); } + @Test + void handleTransportRequestXhrSendWithDifferentRemoteAddress() { + String sockJsPath = sessionUrlPrefix + "xhr"; + setRequest("POST", sockJsPrefix + sockJsPath); + this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); + + // session created + assertThat(this.servletResponse.getStatus()).isEqualTo(200); + verify(this.xhrHandler).handleRequest(this.request, this.response, this.wsHandler, this.session); + + this.session.setRemoteAddress(new InetSocketAddress("127.0.0.1:8080", 8080)); + this.servletRequest.setRemoteAddr("127.0.0.1:9090"); + + resetResponse(); + reset(this.xhrSendHandler); + sockJsPath = sessionUrlPrefix + "xhr_send"; + setRequest("POST", sockJsPrefix + sockJsPath); + this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); + + assertThat(this.servletResponse.getStatus()).isEqualTo(404); + verifyNoMoreInteractions(this.xhrSendHandler); + } + @Test void handleTransportRequestWebsocket() { TransportHandlingSockJsService wsService = new TransportHandlingSockJsService( From b3ef834ae69c552e362fd79ae502ef19136533b9 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 29 Apr 2026 12:10:40 +0100 Subject: [PATCH 3/3] Reliably detect broadcast messages Closes gh-36662 --- .../messaging/simp/user/UserDestinationMessageHandler.java | 6 ++++-- .../simp/stomp/StompBrokerRelayMessageHandlerTests.java | 7 ++++++- .../simp/user/UserDestinationMessageHandlerTests.java | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationMessageHandler.java index c220f492705..f0122d5df9a 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/user/UserDestinationMessageHandler.java @@ -38,6 +38,7 @@ import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.messaging.simp.broker.OrderedMessageChannelDecorator; +import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler; import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.messaging.support.MessageHeaderInitializer; @@ -333,8 +334,9 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec SimpMessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, SimpMessageHeaderAccessor.class); Assert.state(accessor != null, "No SimpMessageHeaderAccessor"); - if (accessor.getSessionId() == null) { - // Our own broadcast + if (accessor.getSessionId() == null || + !accessor.getSessionId().equals(StompBrokerRelayMessageHandler.SYSTEM_SESSION_ID)) { + // Our own or not a broadcast return null; } destination = accessor.getFirstNativeHeader(SimpMessageHeaderAccessor.ORIGINAL_DESTINATION); diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java index 0c5729ffe5c..eb712b655cf 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/StompBrokerRelayMessageHandlerTests.java @@ -245,7 +245,12 @@ class StompBrokerRelayMessageHandlerTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class); verify(handler).handleMessage(captor.capture()); - assertThat(captor.getValue()).isSameAs(message); + + Message actual = captor.getValue(); + assertThat(actual).isSameAs(message); + + accessor = StompHeaderAccessor.getAccessor(actual, StompHeaderAccessor.class); + assertThat(accessor.getSessionId()).isEqualTo(StompBrokerRelayMessageHandler.SYSTEM_SESSION_ID); } @Test diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/UserDestinationMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/UserDestinationMessageHandlerTests.java index 937694fdf3d..cb6f5b03cc3 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/user/UserDestinationMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/user/UserDestinationMessageHandlerTests.java @@ -30,6 +30,7 @@ import org.springframework.messaging.StubMessageChannel; import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageType; +import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler; import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.MessageBuilder; @@ -151,7 +152,7 @@ class UserDestinationMessageHandlerTests { given(this.brokerChannel.send(Mockito.any(Message.class))).willReturn(true); StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.MESSAGE); - accessor.setSessionId("system123"); + accessor.setSessionId(StompBrokerRelayMessageHandler.SYSTEM_SESSION_ID); accessor.setDestination("/topic/unresolved"); accessor.setNativeHeader(ORIGINAL_DESTINATION, "/user/joe/queue/foo"); accessor.setNativeHeader("customHeader", "customHeaderValue"); @@ -175,7 +176,7 @@ class UserDestinationMessageHandlerTests { given(this.brokerChannel.send(Mockito.any(Message.class))).willReturn(true); StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.MESSAGE); - accessor.setSessionId("system123"); + accessor.setSessionId(StompBrokerRelayMessageHandler.SYSTEM_SESSION_ID); accessor.setDestination("/topic/unresolved"); accessor.setNativeHeader(ORIGINAL_DESTINATION, "/user/joe/queue/foo"); accessor.setLeaveMutable(true);