Tengo una aplicación API de socket Java, que maneja las solicitudes de socket de los usuarios y envía respuestas. Tengo un configurador:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { private static final Logger LOGGER = Logger.getLogger(WebSocketConfig.class); @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/queue"); config.setApplicationDestinationPrefixes("/server_in"); config.setUserDestinationPrefix("/user"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/websocket").withSockJS(); } } Cuando envío una respuesta al usuario, hago lo siguiente: this.simpMessagingTemplate.convertAndSend("/queue/private/user_"+secret_key, socketResponse);
En el cliente tengo el siguiente código:
sc.subscribe('/queue/private/user_'+secret_key, function (greeting) { console.log(greeting.body); });Y la respuesta se maneja con éxito. Pero el problema es que algún otro usuario también puede suscribirse al destino "/queue/private/*" y manejar mensajes privados.
sc.subscribe('/queue/private/*', function (greeting) { console.log(greeting.body); });¿Cómo puedo prevenir ese comportamiento?
Si desea que cada usuario tenga un socket y solo reciba mensajes, lo que puede hacer es:
Suscríbase como lo hace al punto final pero con "/ usuario" al frente, por ejemplo
sc.subscribe('/user/queue/websocket, function (greeting) { console.log(greeting.body); });y en el lado del servidor deberías tener un método de descanso:
@RequestMapping(value = "/test", method = RequestMethod.POST) public void test(Principal principal) throws Exception { this.template.convertAndSendToUser(principal.getName(), "/queue/click", ""); }Con esto, todos los usuarios se suscriben a cada canal y solo se notifica al usuario cuando se realiza una llamada de descanso.
La llamada de descanso debe autenticarse para que el Principal tenga el nombre de usuario.
El canal de usuario se administra automáticamente desde Spring, por lo que debe agregarlo de esta manera.
Puede extender ChannelInterceptorAdapter y administrar cada evento individualmente:
public class AuthorizedChannelInterceptorAdapter extends ChannelInterceptorAdapter { @Override public Message<?> preSend(Message<?> message, MessageChannel messageChannel) throws AuthenticationException { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT == accessor.getCommand()) setUserAuthenticationToken(accessor); else if (StompCommand.SUBSCRIBE == accessor.getCommand()) validateSubscription((Authentication) accessor.getUser(), accessor.getDestination()); return message; } private void setUserAuthenticationToken(StompHeaderAccessor accessor) { String token = accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION); accessor.setUser(loadAuthentication(token)); } private Authentication loadAuthentication(String token){ return ....; } private void validateSubscription(Authentication authentication, String destination) { if(...) throw new AccessDeniedException("No permission to subscribe to this topic"); } }En primer lugar, deberá almacenar el objeto de autenticación proporcionado por el cliente en el evento de conexión. Después de esto, cada evento enviado por el cliente tendrá configurado este objeto de autenticación para que pueda usarlo para validar si está autorizado para suscribirse a un canal en particular.