I personally use STOMP but with STOMP (basically a framework for communications on raw WebSocks), the session cookie (from spring security) is send with any message down the socket.
You can use the StompAccessorHeader
like:
@MessageMapping("/agents/start")
public void start(StompHeaderAccessor stompHeaderAccessor) {
log.info("Subscriber Start! {}-{}", stompHeaderAccessor.getUser() != null ? stompHeaderAccessor.getUser().getName() : "ANON", stompHeaderAccessor.getSessionId());
mysessionstore.addSessionId(stompHeaderAccessor.getSessionId());
}
With not using the STOMP framework there may be a way to read the SessionCookie sent per request on the raw socket?
I am not 100% sure but I am guessing you are using the TextWebSocketHandler
implmentation with the:
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage textMessage)
I can see in the source code for WebSocketSession you should be able to get your principal authenticate user there:
https://github.com/spring-projects/spring-framework/blob/0de2833894c24c1e70bde991bad171435c6ecac2/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java#L37
So you authenticate like normal REST like POST "/login" and then that session should be valid for the websockets as well.
You may be able to auth via the socket? You'd have to like make your own socket endpoint to take their credentials and do SecurityContextHolder.getContext().setAuthentication(myAuthUserToken)
but maybe that will then pass a session cookie back? You'd have to test this ofc as I am unsure if it would work shrug.
I personally then make a "store" (a singleton or redis) that holds the user principal and the socketSessionId so I can then match a user to a socket.
You could say store them in a singleton with a HashMap<String,String> userPrincipalNameToSocketSessionId
as a crude way store which socket session belongs to which user.
eg.
A bit like
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage textMessage){
MySessionStore.addSessionToMap(session.getPrincipal(),session.getId());
log.info("Added user {} websocket session {} to the store.",session.getPrincipal(),session.getId());
}
public */MySingletonClass*/ MySessionStore{
@Getter
public static volatile HashMap<String,String> userPrinciapalToSocketMap = new HashMap<>();
//Method to add to map here
public synchronized static addToMap(String principalName,String webSocketSessionId){
...Adds to the map.
}
JWT Stateless Auth System wanting a Socket Session?
As far as I can guess with this one...unless there is a lot of overriding and practically forking/extending lots of Spring classes...
You could make a controller:
Http GET => "/websocket-ticket"
which would return a signed token with the user's principal/username/id for the UX to then pass as a first message after websocket connect.
The socket handler TextMessageHandler
can check the signaute of the token and add it to your HashMap<String,String> principalUserToSessionId
store.
The security issue (unlikely but it is there):
An attacker with XSS could snoop that token and hijack that websocket session. Maybe you win on a race condition (i.e. the MITM takes longer and the token is single use...more wonderful implementation...you now also need a "websocket-ticket-consumed" store...).
https://devcenter.heroku.com/articles/websocket-security
I am feeling this is all heading to the X/Y problem.
Why are you using JWTs for Auth?