@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
如评论中提到的那样,可以在thispr
.中找到一个工作版本。 refacoctoring的高光 如文档的一部分中所述,必须设置安全上下文:
// 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
}
})
}
自定义挂钩像这样使用
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