我试图实现通过WebSocket发送消息的微服务。
我可以将正确的消息发送到子集和身份验证的客户端(将JWT令牌传递给WebClient服务器),但现在我想只向特定用户发送消息。
根据spring官方文档,我可以正确订阅客户端,但没有收到任何消息。
WebSocket服务器配置:
[...]
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors()
.and().csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
;
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/ws/**");
}
[...]
@Slf4j
@Configuration
//@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Qualifier("websocket")
private AuthenticationManager authenticationManager;
@Autowired
public WebSocketConfig(AuthenticationManager authenticationManager)
{
this.authenticationManager = authenticationManager;
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*")
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor headerAccessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(headerAccessor.getCommand()))
{
// log.info("TOKEN: {}", headerAccessor.getNativeHeader("token").toString());
Optional.ofNullable(headerAccessor.getNativeHeader("token")).ifPresent(ah ->
{
String bearerToken = ah.get(0).replace("Bearer ", "");
JWSAuthenticationToken token = (JWSAuthenticationToken) authenticationManager
.authenticate(new JWSAuthenticationToken(bearerToken));
headerAccessor.setUser(token);
});
}
return message;
}
});
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic", "/queue");
}
}
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpTypeMatchers(CONNECT, HEARTBEAT, UNSUBSCRIBE, DISCONNECT).permitAll()
.simpDestMatchers("/app/**", "/topic/**", "/queue/**").authenticated()
.simpSubscribeDestMatchers("/topic/**", "/queue/**", "/user/**").authenticated()
.anyMessage().denyAll();
}
@Override
protected boolean sameOriginDisabled() {
//disable CSRF for websockets for now...
return true;
}
}
simpMessagingTemplate.convertAndSend("/user/" + username + "/queue/private-messages", message);
simpMessagingTemplate.convertAndSendToUser("user","/queue/private-messages", message);
WebSocket客户端配置
[...]
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors()
.and().csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
;
}
[...]
createAndConnectClient
创建连接[...]
public void createAndConnectClient(String accessToken) {
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
StompSessionHandler sessionHandler = new MyStompSessionHandler(stompConfig);
// connect with custom headers
final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
final StompHeaders head = new StompHeaders();
head.add("token", accessToken);
stompClient.connect(serverURL, headers, head, sessionHandler);
}
[...]
@Component
public class MyStompSessionHandler extends StompSessionHandlerAdapter {
private final StompConfig stompConfig;
@Autowired
public MyStompSessionHandler(StompConfig stompConfig) {
this.stompConfig = stompConfig;
}
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
log.info("New session established : " + session.getSessionId());
session.subscribe("/user/queue/private-messages", this);
log.info("Subscribed to /user/queue/private-messages");
}
@Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
log.error("Got an exception", exception);
}
@Override
public Type getPayloadType(StompHeaders headers) {
return Message.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
if (payload != null) {
Message msg = (Message) payload;
log.info("Received : " + msg.getText() + " from : " + msg.getFrom());
} else {
log.info("NULL Payload");
}
}
}
正如我所说,我可以订阅目的地,但我没有收到任何消息。
编辑:我在发送消息时添加了此代码
[...]
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
JWSAuthenticationToken token = null;
try {
token = userService.getToken(message.getTo());
log.info("TOKEN BEFORE SEND: {}", token);
} catch (NotFoundException e) {
e.printStackTrace();
log.error("User isn't connected");
}
headerAccessor.setUser(token);
headerAccessor.setSessionId(userService.getSessionId(message.getTo()));
log.info("SESSION-ID: {}", headerAccessor.getSessionId());
log.info("HEADER: {}", headerAccessor.toString());
simpMessagingTemplate.convertAndSendToUser(message.getTo(), webSocketConfig.getDestination(), message, headerAccessor.getMessageHeaders());
[...]
所以。。。我确信用户(以及SessionID)是相同的。但仍然没有收到任何关于我客户的信息!
工作解决方案:
不是我想要的东西但是。。。它起作用了!¯_(ツ)_/¯
(没有受益于STOMP用户管理机制)
当我订阅目的地时,在我的客户端中,我将用户的名称添加到detionation url
“/queue/messages-”用户。getName()
。
使用该目的地上的convertAndSend
(发送消息且不带标题的服务器端),我可以将私人消息发送给用户
。
不是我想要的东西但是。。。它起作用了!¯_(ツ)_/¯
(没有受益于STOMP用户管理机制)
当我订阅目的地,在我的客户端,我添加到destionation url的用户名:
“/queue/messages-”用户。getName()
。
使用该目的地(发送消息的服务器端)上的convertAndSend
,我可以向用户发送私人消息。
是否为SimpMessageTemplate设置了UserDestinationPrefix。如果没有,请尝试将其设置为“/user”
我相信发生这种情况是因为DefaultSimpUser注册表没有与用户一起更新。如果您看到这一行,它应该添加用户,它不是从STOMP头erAccesssor添加,而是从子协议(即web套接字协议)添加。因此,您必须为webSocket会话设置用户。
private void populateSecurityContextHolder(String
username, List<SimpleGrantedAuthority> grantedAuthorities) {
PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken(
username, null, grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
因此,设置此选项后,用户注册表应该随用户一起更新,因此当消息发送到队列时,当它找到用户时,应该能够将消息发送给用户,而不是因为用户不在场而丢弃消息。
我以个人身份申请了一个微信小程序,在:基础功能 > 订阅消息 > 公共模板库 > 长期订阅 显示没有可用模板。是什么原因导致我没有长期订阅的可用模板?是企业认证才有的吗?还有个疑问是不是小程序推送的模板消息在用户微信上是显示在“服务通知”中,所有小程序的通知都被集中在这里,有没有方法可以单独显示一个小程序的通知(就像服务号一样)
我在配置类中使用spring boot和hibernate。未映射我的实体。请参阅下面的错误。在看了其他一些关于这个的stackoverflow页面后,我仍然无法理解。 我相信以下是正确的:HQL、@实体、@表 错误。 实体。 DAO类 当我将getMessages方法主体替换为以下内容时,它会起作用 数据库表名称为“消息”。 SpringBoot类 Hibernate配置类 我错过了什么?
对调试的任何帮助都将非常感谢。 这是我的堆栈: node.js socket.io express.js passport.js MongoDb react.js 流程: Anna在聊天中发送一条消息(这条消息写入数据库并发布到PubSubtopic“messages”) Node.js express应用程序运行订阅,然后根据消息内容发送给其他应该接收消息的人。 在本例中,与安娜在同一频道的鲍勃
问题内容: 我在android虚拟机中使用以下代码 我收到HttpHostConnectException。不知道为什么?我已将网址中的地址从127.0.0.1更改为10.0.2.2,但仍然收到该异常。我的电脑中安装了wamp服务器,文件“ ReadingFromServer.php”位于“ www”文件夹中。 这是完整的堆栈跟踪 谢谢。 问题答案: 您是否在AndroidManifest.xml
它仍然打印出10个“*”,但后来我得到了这个错误: 但是如果我使用hasNext而不是hasNextLine,它可以正常工作。 所以我想知道为什么hasNext有效,而hasNextLine无效。
问题内容: 我知道静态方法在类级别。因此,我知道我不需要创建实例来调用静态方法。但我也知道我可以将静态方法(如LIKE)称为实例方法。这是我感到困惑的地方,因为我期望从null对象调用静态方法(就像在调用实例方法中一样)。我真的很感谢一些解释,为什么我错了一个期望。 这是示例代码: 问题答案: 通过实例调用静态方法不需要实例存在。只要编译器能够确定变量的类型,它就可以在评估表达式并丢弃结果后静态进