I set up a Spring WebSocket server with the following handler:
public class HandshakeHandler extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(ServerHttpRequest request,
WebSocketHandler handler, Map<String, Object> attributes) {
HttpHeaders headers = request.getHeaders();
System.out.println(headers.toString());
...
}
}
And according to the StompJS docs, I am sending the headers as follows:
let socket = new SockJS("/greetings");
let stompClient = Stomp.over(socket);
stompClient.connect({
key1: "value1",
key2: "value2",
key3: "value3"
}, frame => {
stompClient.subscribe("/user/queue/greetings", greeting => {
console.log(greeting.body);
})
})
However the log does not show the headers. Here is an example of what is logged:
[host:"localhost:8080", connection:"Upgrade", pragma:"no-cache", cache-control:"no-cache", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36", upgrade:"websocket", origin:"http://localhost:8080", sec-websocket-version:"13", accept-encoding:"gzip, deflate, br", accept-language:"en-US,en;q=0.9", sec-websocket-key:"YXYdFogQD4AFDT3gvdSyMg==", sec-websocket-extensions:"permessage-deflate; client_max_window_bits"]
What am I doing wrong?
First of all, your HandshakeHandler code should work as it is. I tried to replicate it by creating an integration test about it as follows (and I was able to get the header values set):
@BeforeEach
public void setup() {
List<Transport> transports = new ArrayList<>();
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
this.sockJsClient = new SockJsClient(transports);
this.stompClient = new WebSocketStompClient(sockJsClient);
this.stompClient.setMessageConverter(new MappingJackson2MessageConverter());
this.headers.add("key1", "value1");
}
@Test
public void getGreeting() throws Exception {
StompSessionHandler handler = new StompSessionHandlerAdapter() {
@Override
public void afterConnected(final StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/user/queue/greetings", new StompFrameHandler() {
@Override
public Type getPayloadType(StompHeaders headers) {
return Greeting.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
Greeting greeting = (Greeting) payload;
try {
assertEquals("Hello, Spring!", greeting.getContent());
} catch (Throwable t) {
fail("Greeting not received");
} finally {
session.disconnect();
}
}
});
}
};
this.stompClient.connect("ws://localhost:{port}/greetings", this.headers, handler, this.port);
}
However, the problem lies in SockJS not being able to send authorization in the header, as it was a security issue.
Reference: https://github.com/sockjs/sockjs-node#authorisation
There is a workaround though, on a proper way of doing it: JSON Web Token (JWT) with Spring based SockJS / STOMP Web Socket
But if you still want to access your header set from SockJS, here is as some snippets that I tried and was able to get header values:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
...
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (accessor != null &&
(StompCommand.CONNECT.equals(accessor.getCommand()) ||
StompCommand.SEND.equals(accessor.getCommand()))) {
String login = accessor.getLogin();
String header2 = accessor.getFirstNativeHeader("key1");
logger.info("Login value: " + login);
logger.info("Some header: " + header2);
}
return message;
}
});
}
}