我如何在春季消息网络网中订阅用户的目的地?

问题描述 投票:0回答:1
@MessageMapping("/private-message") @SendToUser("/queue/messages") fun addUser( @Payload chatMessage: String, principal: Principal ): String { logger.info("Received message: $chatMessage from user: ${principal.name}") return "Hello, ${principal.name}! You sent: $chatMessage" } ... @Bean fun messageAuthorizationManager( messages: MessageMatcherDelegatingAuthorizationManager.Builder ): AuthorizationManager<Message<*>> { val tokenAuthorizationManager = AuthorizationManager<MessageAuthorizationContext<*>> { auth, context -> authenticateAndAuthorize(auth.get(), context.message) } messages .simpTypeMatchers(SimpMessageType.CONNECT, SimpMessageType.DISCONNECT).permitAll() .simpDestMatchers("/app/status", "/topic/status").permitAll() .anyMessage().access(tokenAuthorizationManager) return messages.build() }

将以下前端称为 <StompClient title={`Private Chat - ${username}`} subscribeTopic={`/user/${username}/queue/messages`} publishTopic="/app/private-message" onError={(error) => console.error('Private chat error:', error)} /> ... const subscribeToTopic = (stompClient) => { if (!stompClient?.active) return; try { stompClient.subscribe( subscribeTopic, (message) => { const messageText = message.body.replace(/^Status:\s*/, ""); setMessages((prev) => [...prev, messageText]); }, { "Authorization": `Bearer test.token` }); } catch (err) { handleError(`Failed to subscribe: ${err.message}`); } };

大多数事情似乎都起作用,例如用户名是正确的,但我无法订阅
/user/fakeUser/queue/messages
端点。我可以看到UserDestinationMessageHandler试图将消息发送到适当的

simpDestination

。订阅似乎不起作用?我可以通过调试断点来看到我可以将消息发送到 /app/private-message.
    

如评论中提到的那样,可以在thispr

.
中找到一个工作版本。 refacoctoring的高光

如文档的一部分中所述,必须设置安全上下文:
spring spring-security spring-websocket spring-messaging
1个回答
0
投票
// https://docs.spring.io/spring-framework/reference/web/websocket/stomp/authentication-token-based.html override fun configureClientInboundChannel(registration: ChannelRegistration) { registration.interceptors(object : ChannelInterceptor { override fun preSend(message: Message<*>, channel: MessageChannel): Message<*> { val accessor = MessageHeaderAccessor.getAccessor( message, StompHeaderAccessor::class.java ) if (StompCommand.CONNECT == accessor!!.command) { val authHeader = accessor.getFirstNativeHeader("Authorization") if (authHeader != null && authHeader.startsWith("Bearer ")) { val token = authHeader.substring(7) val authenticatedUser = authenticationProvider.authenticate(BearerTokenAuthenticationToken(token)) accessor.user = authenticatedUser } else { println("Authorization header missing or invalid") } } return message } }) }

我为Stompclient添加了一个自定义钩子 import { useEffect, useRef, useState } from 'react' import { Client } from '@stomp/stompjs' export const useStompClient = (token) => { const stompClientRef = useRef(null) const subscriptionsRef = useRef(new Map()) const [isClientReady, setIsClientReady] = useState(false) useEffect(() => { const client = new Client({ brokerURL: 'ws://localhost:8080/ws', connectHeaders: { Authorization: `Bearer ${token}` }, onConnect: () => { console.log('STOMP client connected') setIsClientReady(true) }, onDisconnect: () => { console.log('STOMP client disconnected') setIsClientReady(false) }, }) stompClientRef.current = client client.activate() return () => { client.deactivate().then(() => console.log('STOMP client deactivated')) subscriptionsRef.current.forEach((sub) => sub.unsubscribe()) subscriptionsRef.current.clear() } }, [token]) const subscribe = (destination, callback) => { if (!isClientReady || !stompClientRef.current) { console.info('STOMP client is not ready yet.') return null } if (subscriptionsRef.current.has(destination)) { console.log(`Already subscribed to ${destination}`) return null } const subscription = stompClientRef.current.subscribe(destination, callback) subscriptionsRef.current.set(destination, subscription) return subscription } const sendMessage = (destination, message) => { if (!isClientReady || !stompClientRef.current) { console.info('STOMP client is not ready yet.') return } stompClientRef.current.publish({ destination: destination, body: message, }) console.log(`Sent message to ${destination}: ${message}`) } return { client: stompClientRef.current, subscribe, sendMessage, isClientReady } }

自定义挂钩像这样使用

import React, {useState, useEffect} from 'react' import {useStompClient} from "./StompContext.jsx" const PrivateChat2 = ({username, token}) => { const {subscribe, sendMessage, isClientReady} = useStompClient(token) const [isReady, setIsReady] = useState(false) const [messages, setMessages] = useState([]) useEffect(() => { if (!isClientReady) return console.log("Trying to subscribe") const subscription = subscribe(`/user/queue/messages`, message => { console.log("Received message", message) setMessages((prev) => [...prev, message.body]) }) setIsReady(true) return () => { subscription?.unsubscribe() } }, [isClientReady]) const sendTestMessage = () => { sendMessage('/app/private-message', "Hello from frontend") } return ( <div> {isReady && <button onClick={sendTestMessage}>Send message to {username}</button>} {messages.map((message, idx) => <div key={idx}>{message}</div>)} </div> ) } export default PrivateChat2

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.