当前位置: 首页 > 知识库问答 >
问题:

为什么我可以订阅目的地,但我没有收到任何消息为该用户与Spring WebSocket STOMP(与Spring Security性和KeyCloak)?

胡承悦
2023-03-14

我试图实现通过WebSocket发送消息的微服务。

我可以将正确的消息发送到子集和身份验证的客户端(将JWT令牌传递给WebClient服务器),但现在我想只向特定用户发送消息。

根据spring官方文档,我可以正确订阅客户端,但没有收到任何消息。

WebSocket服务器配置:

  • WebSecurityConfig.java
[...]
    @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/**");
    }
[...]
  • WebSocketConfig.java
@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");
    }
}
  • WebSocketSecurityConfig。爪哇
@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;
    }
}
  • 在一个Java的类中,我尝试了两个用于发送消息的函数:
simpMessagingTemplate.convertAndSend("/user/" +  username + "/queue/private-messages", message);


simpMessagingTemplate.convertAndSendToUser("user","/queue/private-messages", message);

WebSocket客户端配置

  • WebSecurityConfig.java
[...]
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        super.configure(http);

        http.cors()
                .and().csrf().disable()

                .authorizeRequests()
                .anyRequest()
                .authenticated()

        ;
    }
[...]
  • StompClient。java,其中我使用函数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);
    }
[...]
  • MyStompSessionHandler
@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(发送消息且不带标题的服务器端),我可以将私人消息发送给用户

共有3个答案

艾国安
2023-03-14

不是我想要的东西但是。。。它起作用了!¯_(ツ)_/¯

(没有受益于STOMP用户管理机制)

当我订阅目的地,在我的客户端,我添加到destionation url的用户名:

“/queue/messages-”用户。getName()

使用该目的地(发送消息的服务器端)上的convertAndSend,我可以向用户发送私人消息。

罗学真
2023-03-14

是否为SimpMessageTemplate设置了UserDestinationPrefix。如果没有,请尝试将其设置为“/user”

宗政斌
2023-03-14

我相信发生这种情况是因为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对象调用静态方法(就像在调用实例方法中一样)。我真的很感谢一些解释,为什么我错了一个期望。 这是示例代码: 问题答案: 通过实例调用静态方法不需要实例存在。只要编译器能够确定变量的类型,它就可以在评估表达式并丢弃结果后静态进