I am trying to implement a simple websockets application that can send messages from one endpoint to a specified session established elsewhere. So far I have been able to use the annotation @SendToUser() in order for a client to subscribe to a channel (as discussed in this question: Spring Websockets @SendToUser without login?)
However, I now want to create a separate endpoint that, when called, looks up a user associated with data passed into this endpoint and sends a message to that user regarding this data.
However, I have not been able to accurately determine how to make it so that I can call SimpMessagingTemplate convertAndSendToUser() command because my users do not have a Principal (I'm not using Spring Security).
I've been able to get the simpSessionId from the MessageHeaders passed in to a @MessageMapping endpoint, but now I can't figure out how to use the simpSessionId to send info down from different parts of my application.
I've done some research regarding overriding the determineUser() method of DefaultHandshakeHandler and assigning a randomly generated UUID as the username to a user upon successful websocket handshake (as described in an answer to this question: How to reply to unauthenticated user in Spring 4 STOMP over WebSocket configuration?), but since the principal coming up is null I'm not exactly sure how to properly generate one and assign it to the Principal for use with the application.
I basically need the ability to have anonymous users and send them messages from different parts of the application after they've created a websocket connection.
So for anyone else facing a similar problem, I implemented my own Principal class:
package hello;
import java.security.Principal;
import java.util.Objects;
public class AnonymousPrincipal implements Principal {
private String name;
@Override
public String getName() {
// TODO Auto-generated method stub
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object another) {
if (!(another instanceof Principal))
return false;
Principal principal = (Principal) another;
return principal.getName() == this.name;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
Then, I implemented my own version of DefaultHandshakeHandler:
package hello;
import java.security.Principal;
import java.util.Map;
import java.util.UUID;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
public class CustomHandshakeHandler extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(ServerHttpRequest request,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
Principal principal = request.getPrincipal();
if (principal == null) {
principal = new AnonymousPrincipal();
String uniqueName = UUID.randomUUID().toString();
((AnonymousPrincipal) principal).setName(uniqueName);
}
return principal;
}
}
Now a websocket session gets this principal assigned to it when the Handshake takes place so if the user is anonymous they get an anonymous principal assigned that will allow me to store their name (generated UUID) for later use in other parts of the application.