i am working on JWT Authentication for websockets in springboot. In my JWT filter for WS, I found that Principal is getting null while using other stomp commands such as SEND, SUBSCRIBE. My filter looks like this:
@Component
@RequiredArgsConstructor
public class JWTAuthenticationFilterForWS implements ChannelInterceptor {
private final JwtUtility jwtUtil; // your existing JWT utility
private final UsersRepository usersRepository;
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String token = accessor.getFirstNativeHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
String username = jwtUtil.getUsernameFromToken(token);
// check user exists
usersRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("Invalid JWT"));
// set user for STOMP session
accessor.setUser(new StompPrincipal(username));
} else {
throw new RuntimeException("No JWT token provided");
}
}else {
// For other STOMP commands like SEND, SUBSCRIBE, retrieve user safely
StompHeaderAccessor otherAccessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (otherAccessor != null) {
Principal user = otherAccessor.getUser();
System.out.println("Handling STOMP command " + accessor.getCommand() + " from user: " + user);
// Add any authorization logic for SEND, SUBSCRIBE here if needed
}
}
System.out.println("WebSocket connection established with user: " + accessor.getUser());
return message;
}
}
Whenever stomp calls this filter for send or subscribe, it goes to else block and print “Handling stomp command send null”. That means if i am handling a method for message mapping in controller, i am not able to fetch a principal-its null.
My web socket config:
@Configuration
@EnableWebSocketMessageBroker
@RequiredArgsConstructor
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
private final JWTAuthenticationFilterForWS JWTAuthenticationFilterForWS;
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(JWTAuthenticationFilterForWS);
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
My JS websocket tester code looks like this:
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Chat Tester</title>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/stomp.min.js"></script>
</head>
<body>
<h3>WebSocket Chat</h3>
<label>JWT Token:</label>
<input type="text" id="jwtToken" placeholder="Paste your JWT here" style="width:400px;">
<button onclick="connect()">Connect</button>
<button onclick="disconnect()">Disconnect</button>
<br><br>
<input type="text" id="chatMessage" placeholder="Type a message" style="width:300px;">
<button onclick="sendMessage()">Send</button>
<ul id="messages"></ul>
<script>
let stompClient = null;
function connect() {
const token = document.getElementById("jwtToken").value.trim();
if (!token) {
alert("Please enter JWT token");
return;
}
const socket = new SockJS('http://localhost:8080/chat'); // your endpoint
stompClient = Stomp.over(socket);
stompClient.connect(
{ Authorization: 'Bearer ' + token },
function(frame) {
console.log('Connected: ' + frame);
alert("Connected!");
// Subscribe to your chat topic (replace '1' with chatId)
stompClient.subscribe('/topic/chat', function(messageOutput) {
const msg = JSON.parse(messageOutput.body);
const li = document.createElement("li");
li.innerText = `[${msg.timestamp}] ${msg.sender}: ${msg.content}`;
document.getElementById("messages").appendChild(li);
});
},
function(error) {
console.error('STOMP error: ' + error);
alert('Error connecting: ' + error);
}
);
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect(function() {
console.log("Disconnected");
alert("Disconnected!");
});
}else {
alert("Not connected!");
}
}
function sendMessage() {
const msgInput = document.getElementById("chatMessage").value.trim();
if (!msgInput) return;
if (stompClient && stompClient.connected) {
stompClient.send(
'/app/message',
{},
JSON.stringify({ content: msgInput })
);
document.getElementById("chatMessage").value = '';
} else {
alert("Not connected!");
}
}
</script>
</body>
</html>
Please do help!
I was expecting that after connect command, the session of principal must be stored and used in any other commands, but its not happening