在没有弹簧身份验证的情况下通过 web-socket 多次回复
Posted
技术标签:
【中文标题】在没有弹簧身份验证的情况下通过 web-socket 多次回复【英文标题】:Replying multiple times over web-socket without spring authentication 【发布时间】:2015-05-04 19:19:20 【问题描述】:注意:请参阅问题底部的更新了解我最终得出的结论。
我需要通过发送请求消息的 Web 套接字对请求发送多个响应,第一个响应很快,其他响应在验证数据后(大约 10 到 60 秒后,来自多个并行线程)。
我无法获得稍后的响应以停止通过所有打开的 Web 套接字进行广播。如何让它们仅发送到初始 Web 套接字?或者我应该使用 Spring STOMP 之外的东西(因为,老实说,我想要的只是将消息路由到各种功能,我不需要或想要广播到其他 web 套接字的能力,所以我怀疑我可以写消息经销商本人,即使它正在重新发明***)。
我没有使用 Spring 身份验证(正在改进为遗留代码)。
在初始返回消息上,我可以使用@SendToUser,即使我们没有用户,Spring 也只会将返回值发送到发送消息的 websocket。 (见this question)。
不过,由于响应速度较慢,我想我需要使用 SimpMessagingTemplate.convertAndSendToUser(user, destination, message),但我不能,因为我必须传入用户,我不知道是什么使用 @SendToUser 的用户。我尝试按照in this question 的步骤进行操作,但在未通过身份验证时无法正常工作(在这种情况下,principal.getName() 返回 null)。
我已经为原型大大简化了这一点,所以不用担心同步线程或其他任何事情。我只是希望网络套接字能够正常工作。
这是我的控制器:
@Controller
public class TestSocketController
private SimpMessagingTemplate template;
@Autowired
public TestSocketController(SimpMessagingTemplate template)
this.template = template;
// This doesn't work because I need to pass something for the first parameter.
// If I just use convertAndSend, it broacasts the response to all browsers
void setResults(String ret)
template.convertAndSendToUser("", "/user/topic/testwsresponse", ret);
// this only sends "Testing Return" to the browser tab hooked to this websocket
@MessageMapping(value="/testws")
@SendToUser("/topic/testwsresponse")
public String handleTestWS(String msg) throws InterruptedException
(new Thread(new Later(this))).start();
return "Testing Return";
public class Later implements Runnable
TestSocketController Controller;
public Later(TestSocketController controller)
Controller = controller;
public void run()
try
java.lang.Thread.sleep(2000);
Controller.setResults("Testing Later Return");
catch (Exception e)
为了记录,这里是浏览器端:
var client = null;
function sendMessage()
client.send('/app/testws', , 'Test');
// hooked to a button
function test()
if (client != null)
sendMessage();
return;
var socket = new SockJS('/application-name/sendws/');
client = Stomp.over(socket);
client.connect(, function(frame)
client.subscribe('/user/topic/testwsresponse', function(message)
alert(message);
);
sendMessage();
);
);
这是配置:
@Configuration
@EnableWebSocketMessageBroker
public class TestSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
@Override
public void configureMessageBroker(MessageBrokerRegistry config)
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/queue", "/topic");
config.setUserDestinationPrefix("/user");
@Override
public void registerStompEndpoints(StompEndpointRegistry registry)
registry.addEndpoint("/sendws").withSockJS();
更新:由于信息通过其他 websockets 发送而不是原始套接字的可能性涉及安全问题,我最终向我的小组建议我们不要使用 Spring 4.0 的 STOMP over Web Sockets 实现。我理解 Spring 团队为什么按照他们的方式做,它比我们需要的更强大,但是对我们项目的安全限制足够严格,实际需求也足够简单,所以我们决定走不同的路.这不会使以下答案无效,因此请根据您的项目需求做出自己的决定。至少我们希望大家都了解了这项技术的局限性,无论好坏。
【问题讨论】:
【参考方案1】:为什么不为每个客户使用单独的主题?
客户端生成一个会话 id。
var sessionId = Math.random().toString(36).substring(7);
客户端订阅 /topic/testwsresponse/sessionId,然后向“/app/testws/sessionId”发送消息。
在您的控制器中,您使用@MessageMapping(value="/testws/sessionId") 并删除@SendToUser。您可以使用 @DestinationVariable 在您的方法中访问 sessionId。 控制器向 /topic/testwsresponse/sessionId 发送更多响应。本质上,当您使用用户目的地时,Spring 在内部会做类似的事情。由于您不使用 Spring 身份验证,因此您不能依赖此机制,但您可以轻松实现自己的,如上所述。
var client = null;
var sessionId = Math.random().toString(36).substring(7);
function sendMessage()
client.send('/app/testws/' + sessionId, , 'Test');
// hooked to a button
function test()
if (client != null)
sendMessage();
return;
var socket = new SockJS('/application-name/sendws/');
client = Stomp.over(socket);
client.connect(, function(frame)
client.subscribe('/topic/testwsresponse/' + sessionId, function(message)
alert(message);
);
// Need to wait until subscription is complete
setTimeout(sendMessage, 1000);
);
);
控制器:
@Controller
public class TestSocketController
private SimpMessagingTemplate template;
@Autowired
public TestSocketController(SimpMessagingTemplate template)
this.template = template;
void setResults(String ret, String sessionId)
template.convertAndSend("/topic/testwsresponse/" + sessionId, ret);
@MessageMapping(value="/testws/sessionId")
public void handleTestWS(@DestinationVariable String sessionId, @Payload String msg) throws InterruptedException
(new Thread(new Later(this, sessionId))).start();
setResults("Testing Return", sessionId);
public class Later implements Runnable
TestSocketController Controller;
String sessionId;
public Later(TestSocketController controller, String sessionId)
Controller = controller;
this.sessionId = sessionId;
public void run()
try
java.lang.Thread.sleep(2000);
Controller.setResults("Testing Later Return", sessionId);
catch (Exception e)
刚刚测试过,按预期工作。
【讨论】:
我有一些安全问题。首先是有人可能会找出其他人正在使用的随机字符串并收听对话。第二个是某人可以注册一些(或全部,也许)潜在字符串并听取所有响应。对于这个项目来说,即使很幸运并听取其他人的回应也不够安全。我真的只需要通过单个 Web 套接字发送消息,我开始认为 STOMP 的 Spring 实现不会这样做.. 您不必担心安全问题。只需使用足够长的随机字符串或使用 UUID 作为会话 ID。 en.wikipedia.org/wiki/… 基本上黑客平均需要 170 亿年才能破解你的系统。 我猜你的 gmail 密码比黑客猜随机会话 id 的可能性更大。 是的,但是如果他们能以某种方式找到一个人的会话 ID 怎么办?我还没有完全考虑清楚,但我担心,随着所有数据的黑客攻击正在发生,我不得不担心安全性。 我同意你的看法。但是安全问题与我提供的解决方案无关(问题是关于防止消息广播)。安全是一个单独的问题。话虽如此,您只需要使会话 id 足够长,即使没有任何其他身份验证机制,该解决方案也将是完全安全的,只要您使用 WSS(WebSocket secure: WS over TLS)【参考方案2】:这不是完整的答案。只是一般的考虑和建议。 你不能通过同一个套接字做不同的事情或连接类型。为什么不针对不同的工作使用不同的插座?有些有身份验证,有些没有。有些用于快速任务,有些用于长时间执行。
【讨论】:
因为在幕后,请求会启动单独的线程来完成更持久的工作,如果我关闭第一个连接,我将无法轻松地再次与这些线程交谈。我现在所拥有的是向数据库写入内容的线程和 ajax 调用每隔几秒就拉出农场中的任何一个 Web 服务器,它们读取数据库并查看是否有任何完成。通过使用一个表示打开的 Web 套接字,我将其锁定到一台服务器(一段时间),并让线程能够直接响应,而无需涉及数据库。以上是关于在没有弹簧身份验证的情况下通过 web-socket 多次回复的主要内容,如果未能解决你的问题,请参考以下文章