SpringMVC Websockets Messaging User Authentication with Spring Security

本文探讨了Spring Security下WebSocket的安全配置问题,特别是在使用Spring的convertAndSendToUser方法时如何避免未授权用户订阅他人消息的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



I have seen a couple of threads about this issue, but none of them seem to really answer the question directly.

Background, I have spring security installed, working, and running smoothly in other parts of the application. My username is "developer".

Running on Java 7, Glassfish 4, Spring 4, and using Angular + StompJS

Let's get some code here:

package com.myapp.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfig extends AbstractWebSocketMessageBrokerConfigurer {

    public final static String userDestinationPrefix = "/user/";

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/stomp").withSockJS().setSessionCookieNeeded(true);
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app");
        //registry.enableStompBrokerRelay("/topic,/user");
        registry.enableSimpleBroker("/topic", "/user");
        registry.setUserDestinationPrefix(userDestinationPrefix);


    }


}

Ok, now here is a controller, to send out stuff every 3 seconds:

import org.springframework.messaging.simp.SimpMessagingTemplate;



@Autowired
private SimpMessagingTemplate messagingTemplate;



@Scheduled(fixedDelay = 3000)
public void sendStuff () 
{

    Map<String, Object> map = new HashMap<>();
    map.put(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
    System.out.print("Sending data! " + System.currentTimeMillis());
    //messagingTemplate.convertAndSend("/topic/notify", "Public: " + System.currentTimeMillis());

    messagingTemplate.convertAndSendToUser("developer", "/notify", "User: " + System.currentTimeMillis());
    messagingTemplate.convertAndSendToUser("notYou", "/notify", "Mr Developer Should Not See This: " + System.currentTimeMillis());
}

And finally the JavaScript using SockJS

    var client = new SockJS('/stomp');
    var stomp = Stomp.over(client);
    stomp.connect({}, function(s) {

        //This should work
        stomp.subscribe('/user/' + s.headers['user-name'] + '/notify', console.debug);

        //This SHOULD NOT
        stomp.subscribe('/user/notYou/notify', console.debug);

    });
    client.onclose = $scope.reconnect;

And finally, for kicks, the pom.xml

    <dependency>
        <groupId>javax.websocket</groupId>
        <artifactId>javax.websocket-api</artifactId>
        <version>1.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-messaging</artifactId>
        <version>4.0.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-websocket</artifactId>
        <version>4.0.6.RELEASE</version>
    </dependency>

Here is what does work:

  1. I can produce wonderfully communication back and forth between the client and the server
  2. It's fast
  3. messagingTemplate.convertAndSend and messagingTemplate.convertAndSendToUser

This is the problem (noted above): Anyone can subscribe to other users feeds.

Now, there are a few other versions of this floating around, I will list them below, and explain why the answers are all wrong:

What are the security issues around an open websocket connection?

Spring websocket with stomp security - every user can subscribe to any other users queue?

Websocket: How To Push A Message To A Target User

Here's the problem:

  • Look at messagingTemplate.convertAndSendToUser - All that does is add the "user prefix" and then the username provided and then usemessagingTemplate.convertAndSend which does not apply security.

  • Then people say that "you need to use spring security just like everywhere else" - the problem here is A) that I am SENDING data to the client asynchronously, so B) I will be using this code completely outside of the user's session, possibly from a different user (say to send a notification to another logged in user).

Let me know if this is too closely related to a different post, but I this is a big problem for me and I wanted to do this justice.

I can get more details though if anyone needs more details.

share | improve this question
 
 

1 Answer 1

I think you must make these changes:

1) You must not enableSimpleBroker for "/user" because it's a special queue handled automatically by the broker

2) if the server uses for example the annotation "@SendToUser("/queue/private")" the client must subscribe to the queue "/user/queue/private" : you must not prepend the username in the queue because it's a transparent operation handled by the broker

I'm sure this works correctly because I'm using it in my setup.

I've not tried with the convertAndSendToUser() method but since its semantic should be the same of the annotation, it should work too.

share | improve this answer
 
 
Great response, thanks so much! I will go ahead and try that now and update you/mark it as solves as soon as I know. –  Nitroware Jul 29 at 17:31
1 
So I tried doing exactly what you had recommended and still it did not work. The critical problem here between our two situations is this: You are using@SendToUser which already has the current users session and scope, and likely the user session. I am trying to send notifications, possibly from another, to a different user usingconvertAndSendToUser() which is where i am getting my problems from –  Nitroware Jul 29 at 18:55
在使用uniapp打包H5页面后,前端通过服务器发送事件源(Server-Sent Events, SSE)与Java后端进行实时数据通信。下面是一个简单的前端(JavaScript)和后端(Java)实现的步骤: **前端(HTML + JavaScript)部分:** ```html <!DOCTYPE html> <html lang="en"> <head> <title>Realtime Communication</title> </head> <body> <div id="realtimeData"></div> <script> // 创建一个事件监听器来处理来自服务器的数据 const source = new EventSource('/stream?id=your_unique_id'); source.addEventListener('message', (event) => { // 解析接收到的消息 const data = JSON.parse(event.data); document.getElementById('realtimeData').innerHTML += `Received: ${data.message}\n`; }); // 错误处理 source.addEventListener('error', () => { console.error('Error occurred while connecting to server'); }); </script> </body> </html> ``` 在这个例子中,`EventSource`会周期性地从`/stream?id=your_unique_id`这个URL获取实时更新。当服务器有新的消息时,它会触发`message`事件。 **后端(Java - SpringBoot 示例):** 首先,你需要引入SpringWebSockets支持(如果你还没有的话): ```java import org.springframework.web.socket.messaging.SessionMessage; import org.springframework.web.socket.messaging.StompSession; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketService; import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; // ... @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); } @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { // ...配置WebSocket容器... return new ServletServerContainerFactoryBean(); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/stream") .withSockJS() .setAllowedOrigins("*") // 允许跨域请求 .addInterceptors(new YourInterceptor()); // 添加自定义拦截器 } } // 自定义WebSocket处理器 @Component public class RealtimeEventHandler implements WebSocketHandler { private final Map<String, StompSession> sessions = new ConcurrentHashMap<>(); @Override public void handleTextMessage(StompSession session, TextMessage message) { String id = session.getId(); // 获取session ID // 这里假设你有一个推送服务,将新数据转换成JSON并发送给特定用户 String newData = ...; // 例如从数据库查询 sendMessageTo(session, "message", newData); // 发送消息 } private void sendMessageTo(StompSession session, String event, String payload) { try { session.sendMessage(new SessionMessage<>(event, payload)); } catch (IOException e) { log.error("Failed to send message", e); } } } ``` 这里我们创建了一个`RealtimeEventHandler`类作为WebSocket处理器,负责接收客户端的连接,并处理消息发送。`handleTextMessage`方法会在服务器向指定ID的客户端发送实时数据时被调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值