import { Client } from '@stomp/stompjs';
const WebSocketClient = () => {
...
const stompClient = new Client({
brokerURL,
onConnect: () => {
setConnected(true);
setError('');
setCanRetryGreetings(true);
// Subscribe to public status topic
stompClient.subscribe('/topic/greeting', (message) => {
setMessages(prev => [...prev, `Status: ${message.body}`]);
});
// Announce presence
stompClient.publish({
destination: '/app/hello',
body: 'Client connected'
});
},
onDisconnect: () => {
setConnected(false);
setCanRetryGreetings(true);
},
onError: (err) => {
setError(`Connection error: ${err.message}`);
}
});
问题是我不确定如何设置授权标题。这是工作邮递员请求的样子...
如果我卸下标头,这是完美的,失败了。但是看来您不能为Stomp中的握手进行自定义标题。那么我该如何工作呢?
完整项目可在此处提供
尝试。
看起来像是问题的是连接具有auth标题
boolean granted = this.authorizationStrategy.isGranted(authentication.get());
表示,它不是使用auth标题来确定请求是否来自已认证的某人。 我这样做了...首先订阅和发布使用标头发送授权令牌...
client.publish(
{
destination: publishTopic,
body: JSON.stringify({
message: statusMessage,
name: "Test Message"
}),
headers: {
"Authorization": `Bearer test.token`
}
}
);
...
stompClient.subscribe(
subscribeTopic,
(message) => {
...
},
{
"Authorization": `Bearer test.token`
});
在弹簧侧的next我需要添加一些东西以从标题中正确创建授权者...
private fun authenticateAndAuthorize(auth: Authentication?, message: Message<*>): AuthorizationDecision {
val nativeHeaders = message.headers["nativeHeaders"] as? Map<String, List<String>>
val authHeader = nativeHeaders?.get("Authorization")?.firstOrNull()
return try {
if (auth != null && auth.isAuthenticated && auth !is AnonymousAuthenticationToken) {
logger.debug("User already authenticated: ${auth.name}")
AuthorizationDecision(true)
} else if (authHeader != null && authHeader.startsWith("Bearer ")) {
val token = authHeader.substring(7)
val authenticatedUser = authenticationProvider.authenticate(BearerTokenAuthenticationToken(token))
SecurityContextHolder.getContext().authentication = authenticatedUser
logger.debug("Successfully authenticated token for user: ${authenticatedUser.name}")
AuthorizationDecision(true)
} else {
logger.debug("No valid authentication found")
AuthorizationDecision(false)
}
} catch (e: Exception) {
logger.error("Authentication failed: ${e.message}", e)
AuthorizationDecision(false)
}
}
@Profile("secure")
@Configuration
@EnableWebSocketSecurity
class WebSocketSecurityConfig(jwtDecoder: JwtDecoder) {
private val logger: Logger = LoggerFactory.getLogger(WebSocketSecurityConfig::class.java)
private val authenticationProvider = JwtAuthenticationProvider(jwtDecoder)
@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()
}
private fun authenticateAndAuthorize(auth: Authentication?, message: Message<*>): AuthorizationDecision {
val nativeHeaders = message.headers["nativeHeaders"] as? Map<String, List<String>>
val authHeader = nativeHeaders?.get("Authorization")?.firstOrNull()
return try {
if (auth != null && auth.isAuthenticated && auth !is AnonymousAuthenticationToken) {
logger.debug("User already authenticated: ${auth.name}")
AuthorizationDecision(true)
} else if (authHeader != null && authHeader.startsWith("Bearer ")) {
val token = authHeader.substring(7)
val authenticatedUser = authenticationProvider.authenticate(BearerTokenAuthenticationToken(token))
SecurityContextHolder.getContext().authentication = authenticatedUser
logger.debug("Successfully authenticated token for user: ${authenticatedUser.name}")
AuthorizationDecision(true)
} else {
logger.debug("No valid authentication found")
AuthorizationDecision(false)
}
} catch (e: Exception) {
logger.error("Authentication failed: ${e.message}", e)
AuthorizationDecision(false)
}
}
}
@MessageMapping("/private-message")
@SendToUser("/chat/messages")
fun addUser(
@Payload chatMessage: String,
principal: Principal
): String {
return "Hello, ${principal.name}! You sent: $chatMessage"
}
@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfig : WebSocketMessageBrokerConfigurer {
...
override fun configureClientInboundChannel(registration: ChannelRegistration) {
registration.interceptors(object : ChannelInterceptor {
override fun preSend(message: Message<*>, channel: MessageChannel): Message<*> {
val accessor = MessageHeaderAccessor.getAccessor(message, SimpMessageHeaderAccessor::class.java)
val messageType = accessor?.messageType
// Only process CONNECT, SUBSCRIBE, and SEND messages
if (messageType == SimpMessageType.CONNECT ||
messageType == SimpMessageType.SUBSCRIBE ||
messageType == SimpMessageType.MESSAGE) {
val auth = SecurityContextHolder.getContext().authentication
if (auth != null && auth.isAuthenticated) {
accessor?.user = auth
}
}
return message
}
})
}
}
现在似乎正如预期的。