Currently, the WebSocket / stomp security reference documentation contains two unhelpful lines about authenticating a user on a WebSocket. They basically seems to suggest spring-websocket can only be used in a website where a user is already authenticated.
AFAIK, this is not correct given the advances in spring-session and spring-security.
I've found that finding out how to authenticate a user over spring-websocket takes you through a web of examples and gists representing various versions of spring-security, spring-session and spring-websocket, stackoverflow questions and Github issues.
It's an absolute maze. And it's something you want to do right, since if you screw up, you're opening yourself up to all sorts of disasters.
I would recommend writing documentation that explains how to authenticate, authorize, reconnect and maintain secure (csrf etc) a websocket based connection assuming a server that serves both mobile devices and browsers, discussing both server and client.
The assumption that websocket is the primary channel for mobile app communication needs to be at the basis of our understanding of how this will be used most. It's not just about a website with a chat pane.
I realize that the client side is basically outside of the scope of the core documentation. So, this could be placed in guides perhaps.
What I find missing currently:
It's not entire clear if this is the right place for this issue since this concern overlaps spring-session, spring-security and spring-websocket.
I am wanting to implement websockets in my app and found out everything that you have written about is true. No good examples on how to implement websocket auth when I am using restful api and JWT tokens to secure my api. Spring docs only have examples if you use sessions.
I'm not even clear on what header to supply to a stomp websocket message to get a Principal. I've seen various headers described in various examples and various values of them:
There doesn't seem to be a clear description on which one to use and how. A LOT of assumptions are made about the knowledge of the developer implementing this stuff. There's also the unfortunate and incorrect assumption that all users use websocket as a secondary channel for a website, providing access to cookies etc. i think this assumption is the primary reason a lot of this documentation is screwed up.
The only thing that's clear to me is what a session id is. I'm supplying SPRING.SESSION.ID right now as a header on a message, but not getting an authenticated user at the end of that process. Wasting so much time grappling in the dark. Incredibly disappointing.
_You could take a look at this: Github project
which is based on a "hack" in this blog post_
They have created a class AbstractSecurityWebSocketMessageBrokerConfig
which allows setting custom securityContextChannelInterceptor
Also check out this issue: https://github.com/spring-projects/spring-security/issues/3217
asaarnak thank you for that link. this makes much clear but what about a binary web sockets i.e.
what about situations where no stomp of rabbt mq or any text based protocols are used , instead only pure binary messages over websockets. What class "Configurer" do we need to exted from in this case?. It seems that AbstractWebSocketMessageBrokerConfigurer was beeing particularlly created with the STOMP in mind since it implements "WebSocketMessageBrokerConfigurer" which enforces me to register a basic "StompEndpoint" but what if i am not using STOMP? i am missing the class that will handle pure binary messages. i.e. something like AbstractWebSocketMessageBrokerConfigurer but for binary protocols only.
Found an example how to use spring-websockets without STOMP.
Spring security does'nt support websockets without STOMP.
For spring-security you should extend the WebSocketConfigurer, but propably easier to just use STOMP without sockJs fallback.
asaarnak many thanks, i guess i needed to clarify .I already did that, i.e. my app already extend WebSocketConfigurer and i am able to pass binary messages back and forth what does not work is the authentication i.e. when i after successfully creating my token and set it into the SecurityContextHolder succesefully ,
token.setAuthenticated(true);
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(token);
SecurityContextHolder.setContext(securityContext);
sessionStrategy.onAuthentication(
authentication,
httpServletRequest,
httpServletResponce
);
after the next binary payload is received within the same websocket when i try to access the the security context holder it is always null.
SecurityContext securityContext = SecurityContextHolder.getContext();
testUser = (Users) securityContext.getAuthentication().getPrincipal();
If i do the same step but use HTTP as transport instead then it all works. Taking into account that i use the same AuthenticationProvider in bought cases i.e. only different transport. For me that means that the Security Context Holder is somehow living in the HTTP session and when that is cleared my security context is also cleared so i was hoping to find an example showing how to prevent that.
You could try adding custom HandshakeInterceptor,
try create something like SecurityContextChannelInterceptor, that is used by the AbstractSecurityWebSocketMessageBrokerConfig.
And then add after addHandler with:
.addHandler(yourWebSocketHandler)
.addInterceptors(yourHandshakeInterceptor)
Most helpful comment
I'm not even clear on what header to supply to a stomp websocket message to get a Principal. I've seen various headers described in various examples and various values of them:
There doesn't seem to be a clear description on which one to use and how. A LOT of assumptions are made about the knowledge of the developer implementing this stuff. There's also the unfortunate and incorrect assumption that all users use websocket as a secondary channel for a website, providing access to cookies etc. i think this assumption is the primary reason a lot of this documentation is screwed up.
The only thing that's clear to me is what a session id is. I'm supplying SPRING.SESSION.ID right now as a header on a message, but not getting an authenticated user at the end of that process. Wasting so much time grappling in the dark. Incredibly disappointing.