17

I have a simple spring application with websocket functionality and everything works so far. Now I want to send a message from my server to a specific client using the @SendToUser annotation. This gives me the error "Ignoring message, no principal info available". I understand that i have no login whatsoever on my server, so every user is "anonymous" and does not have a principal (I am not using spring security for now). But every user has a session-id. Isnt it possible to use the session id somehow to differentiate between users? How can i achieve that so my users get a principal which corresponds to the session-id?

kentobi
  • 491
  • 1
  • 7
  • 10

5 Answers5

16

Use @SendToUser and add "/user/" in front of queue when subscribing (only subscriber side). Rest works magic :-)

Instead of

Java Server: @SendTo("/topic/showResult")

and

JS Client: stompClient.subscribe('/topic/showResult', function(calResult){  ....

use:

Java Server: @SentToUser("/topic/showResult")

and

JS Client: stompClient.subscribe('/user/topic/showResult', function(calResult){ ....

R.A
  • 1,813
  • 21
  • 29
  • Ah, that works. I can't prove it, but I suspect that, if I was using spring login security, and I had two tabs open that shared the session id, I would get the message in both tabs. As it is, without the security, I only get it in the tab I am working on, and I don't have to worry about people subscribing to messages they don't have permission to see. – Guy Schalnat May 01 '15 at 20:32
10

I think a solution might be to avoid using @SendToUser and use raw SimpMessagingTemplate and to send messages to a destination that you control for open sessions.

For eg. assuming that you had some identity for a new websocket session, you can subscribe to a queue with that identifier in the queue name:

stomp.subscribe("/queue/chats" + "-" + mycustomidentifier, onmessage);

Now, on the Spring websocket listener side, you can direct your responses using SimpMessagingTemplate:

@Controller
public class MyController {


    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/chats")
    public void handleChat(@Payload ChatMessage message) {
        this.simpMessagingTemplate.convertAndSend("/queue/chats-" + "mycustomidentifier", "[" + getTimestamp() + "]:" + message.getMessage());
    }
....
Biju Kunjummen
  • 49,138
  • 14
  • 112
  • 125
  • thanks that seems like a good idea I'll try it out the next days – kentobi Aug 02 '14 at 11:25
  • ok i did that and it works now. the way i do it is by generating a client-id on client-side and sending it as header info when connecting to websocket server – kentobi Aug 05 '14 at 08:42
  • 6
    You may or may not have a security problem here, but it seems to me I could go into the browser and edit the script on the fly to guess someone else's "mycustomidentifier" and watch their private messages. – Guy Schalnat May 01 '15 at 20:23
8

Building on Biju's answer and using the Stomp generated session id (thanks, mariusz2108 in his answer to a similar question), here's what worked for me (based on the canonical example from Spring)

SpringFramework client:

private SimpMessagingTemplate template;

@Autowired
public GreetingController(SimpMessagingTemplate template) {
    this.template = template;
}

@MessageMapping("/hello")
public void greeting(HelloMessage message, @Header("simpSessionId") String sessionId) throws Exception {
    template.convertAndSend("/queue/greeting-"+sessionId, new Greeting("Hello, " + message.getName()));
}

JavaScript client:

function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        var sessionId = /\/([^\/]+)\/websocket/.exec(socket._transport.url)[1];
        console.log("connected, session id: " + sessionId);
        stompClient.subscribe('/queue/greeting-'+sessionId, function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });
}

Instead of the Stomp session id you could use your web container's Session ID (e.g. JSESSIONID) but now that cookie is not by default accessible from JavaScript (for Tomcat) this is a more difficult prospect.

Community
  • 1
  • 1
AndrewL
  • 2,034
  • 18
  • 18
  • 1
    I do not know why the session ID is even hidden behind the socket...? – Mr. Polywhirl Mar 14 '19 at 18:54
  • I just came to take your sessionId Regex. I don't know why there isn't a socket.sessionId property – GabrielBB Sep 06 '19 at 00:07
  • 1
    The provided regex does not work if your endpoint contains /websocket/ elsewhere. This one does: `var sessionId = /\/([^\/]+)\/websocket\/?$/.exec(socket._transport.url)[1];` – annih May 12 '21 at 12:11
2

Try this. It worked for me

@Autowired
private SimpMessagingTemplate messagingTemplate;

@MessageMapping("/getHello")
public void sendReply( MessageHeaders messageHeaders, @Payload String message, @Header(name = "simpSessionId") String sessionId){
        messagingTemplate.convertAndSendToUser(sessionId, "/queue/hello", "Hello "+ message, messageHeaders);
}
airush
  • 722
  • 9
  • 19
  • What do you subscribe to on the client side? I have a similar setup but messages get prepended with "/user/{sessionId}", so I'd have to listen to "/user/{sessionId}/queue/hello", but I can't because I don't have the session id on the client side. – GreyScreenOfMeh Nov 14 '18 at 12:17
  • 1
    @GreyScreenOfMeh Subscribe to "/user/queue/hello". I don't have session ID on the client side too. – airush Nov 15 '18 at 08:44
0

This solution works for me.

Context: there is no security in your application and you want to send message to user by session-id. Another words user-id=session-id in our case.

private final SimpMessageSendingOperations template;

@MessageMapping("/test")
public void testNotification(@Header("simpSessionId") String sessionId, String name) {
    String payload = "Hello, " + name + "!";
    template.convertAndSendToUser(sessionId, "/ws/notification/message", payload, createHeaders(sessionId));
}

private MessageHeaders createHeaders(String sessionId) {
    SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
    headerAccessor.setSessionId(sessionId);
    headerAccessor.setLeaveMutable(true);
    return headerAccessor.getMessageHeaders();
}

and in such way javascript snippet looks:

stompClient.connect(header, function (frame) {
    console.log('Connected: ' + frame);
    stompClient.subscribe('/user/ws/notification/message', function (greeting) {
        console.log(greeting.body);
    });
});
Vladimir
  • 525
  • 5
  • 15