Spring Websockets @SendToUser 不需要登录吗?

17

我有一个简单的Spring应用程序,具有WebSocket功能,到目前为止一切都运行正常。现在我想使用@SendToUser注释从服务器向特定客户端发送消息。但是这会导致错误“忽略消息,没有可用的主体信息”。我理解我在服务器上没有任何登录信息,因此每个用户都是“匿名”的,并且没有主体(暂时不使用Spring Security)。但是每个用户都有一个会话ID。难道不能使用会话ID来区分用户吗?如何实现让我的用户获得与会话ID对应的主体呢?

5个回答

16

使用@SendToUser,并在订阅时在队列前面添加“/user/”(只在订阅方面)。 其余的就像魔法一样运作 :-)

而不是

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

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

用法:

Java 服务器端:@SentToUser("/topic/showResult")

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


啊,这很好。我无法证明,但我怀疑如果我使用Spring登录安全性,并且我有两个共享会话ID的选项卡,那么我将在两个选项卡中都收到消息。由于没有安全性,我只在我正在工作的选项卡中收到它,而且我不必担心人们订阅他们没有权限查看的消息。 - Guy Schalnat

10

我认为一个解决方案可能是避免使用@SendToUser,改用原始的SimpMessagingTemplate并向您控制的目标发送消息以获取开放会话。

例如,假设您有一些与新的WebSocket会话相关的身份标识,您可以订阅具有该标识符的队列:

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

现在,在Spring WebSocket监听器方面,你可以使用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());
    }
....

谢谢,这听起来是个好主意,我会在接下来的几天尝试一下。 - kentobi
好的,我已经做到了,现在它可以工作了。我的做法是在客户端生成一个客户端ID,并在连接到WebSocket服务器时将其作为头信息发送。 - kentobi
6
这里可能存在安全问题,我可以进入浏览器并即时修改脚本来猜测别人的"mycustomidentifier"并查看他们的私人信息。 - Guy Schalnat

8

Biju的回答的基础上,利用Stomp生成的会话ID(感谢mariusz2108在类似问题的回答中提供的帮助),以下是我使用Spring的标准示例得出的结果。

SpringFramework客户端:

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客户端:

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);
        });
    });
}

您可以使用Web容器的会话ID(例如JSESSIONID)代替Stomp会话ID,但是现在该Cookie 默认情况下无法从JavaScript访问(对于Tomcat而言),这是一个更困难的问题。


1
我不知道为什么会话ID甚至隐藏在套接字后面...? - Mr. Polywhirl
我只是来获取你的sessionId正则表达式。我不知道为什么没有socket.sessionId属性。 - GabrielBB
1
提供的正则表达式如果您的端点在其他地方包含/ websocket /,则无法工作。这个可以:var sessionId = / \ /([^ \ /] +)\ / websocket \ /?$ / .exec(socket._transport.url)[1]; - annih

2

试试这个。它对我有用。

@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);
}

你在客户端订阅了什么?我有一个类似的设置,但是消息会以“/user/{sessionId}”为前缀,所以我必须监听“/user/{sessionId}/queue/hello”,但是我无法在客户端获取到会话ID。 - GreyScreenOfMeh
1
@GreyScreenOfMeh 订阅 "/user/queue/hello"。 我在客户端也没有会话 ID。 - airush

0

这个解决方案对我很有效。

背景:您的应用程序中没有安全性,您想通过会话ID向用户发送消息。换句话说,在我们的情况下,用户ID=会话ID

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();
}

这样,JavaScript 代码片段看起来就像这样:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接