How client and server mapping message on Spring boot websocket - spring-boot

I cloned a chat application project that uses spring boot websocket on github.
Here is code:
#MessageMapping("/chat.private.{username}")
public void filterPrivateMessage(#Payload ChatMessage message, #DestinationVariable("username") String username, Principal principal) {
message.setUsername(principal.getName());
simpMessagingTemplate.convertAndSend("/user/" + username + "/exchange/amq.direct/chat.message", message);
}
Example: username variable is: foo#gmail.com, it mean the link to for client subscribe should be: /user/foo#gmail.com/exchange/amq.direct/chat.message
But in client code:
chatSocket = Stomp.over(new SockJS(url)); //use sockjs and stompjs
chatSocket.subscribe("/user/exchange/amq.direct/chat.message"
I do not understand how to the application can send to correct client, when the client listen the different url (without foo#gmail.com).
Can someone explain to me?
Thanks.

The key is the /user/ prefix in the subscribe url, which will be transformed by Spring to deliver the message to the specific user. It is described in the User Destinations section in the docs:
An application can send messages targeting a specific user, and Spring’s STOMP support recognizes destinations prefixed with /user/ for this purpose. For example, a client might subscribe to the destination /user/queue/position-updates. This destination will be handled by the UserDestinationMessageHandler and transformed into a destination unique to the user session, e.g. /queue/position-updates-user123. This provides the convenience of subscribing to a generically named destination while at the same time ensuring no collisions with other users subscribing to the same destination so that each user can receive unique stock position updates.

Related

Spring Webflex: Push Server Sent Event to Specific Users

I have been following this for reference. I am developing a spring-boot app which will have authenticated users. Once logged in, a user will subscribe to an event by visiting a specific URL.
This spring-boot app will also either support MQTT (or maybe just HTTP requests) in which information for a specific user will be sent. I would like to then display this sent information to the user using web flux/SSE if the user has subscribed.
Many users can be logged in at any given time, and they will have all subscribed to the updates. How do I manage all the different sinks for each logged in user?
I believe it's possible to get the current user when they visit the authenticated URL, but what's a method of storing all of the sinks for each logged in user?
I appreciate it.
You already got the answer in the comment section.
Lets assume that this is the message format you would be publishing.
public class Message {
private int intendedUserId;
private String message;
// getters and setters
}
Just have 1 processor and sink from the processor.
FluxProcessor<Message> processor;
FluxSink<Message> sink;
Push all the messages via the sink.
sink.next(msg);
Your controller would be more or less like this. Here I assume you have some method to get the user id authtoken.getUserId(). Here the filter is part of the Flux.
#GetMapping(value = "/msg", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Message> getMessages(){
return processer
.filter(msg -> msg.getIntendedUserId() == authtoken.getUserId());
}

Vertx SockJs Eventbus Authentication

I'm trying to make a sock.js connection from the frontend to the vertx backend.
my initial try looked like this:
let token = '<the token>';
let data = {'Authorization' : 'Bearer ' + token};
let eb = new EventBus("http://localhost:8080/eventbus");
eb.onopen = function () {
eb.registerHandler('notifications', data, (err, msg) => {
// handle the response
});
}
this doesn't work since I need to send the auth data on EventBus creation, even though the official sock.js documentation states that this is not supported. Obviously now sending new EventBus("http://localhost:9090/eventbus", data) doesn't work either.
https://github.com/sockjs/sockjs-node#authorisation
my backend handler for this:
final BridgeOptions bridgeOptions = new BridgeOptions()
.addOutboundPermitted(new PermittedOptions().setAddress("notifications"))
final SockJSHandler sockJSHandler = SockJSHandler.create(vertx).bridge(bridgeOptions, event -> {
event.complete(true);
});
router.route("/eventbus/*").handler(ctx -> {
String token = ctx.request().getHeader("Authorization"); // null
});
router.route("/eventbus/*").handler(sockJSHandler);
whatever I tried the header field Authroization is always null.
What is the standard way to authenticate the sock.js connection and register to an eventbus request in vertx?
SockJS uses WebSockets by default. You can't add custom headers (Authorization, etc) using JavaScript WebSocket API. Read this thread for more explanation.
I see 2 ways, how you can add authorization:
Just add token parameter to URL:
let eb = new EventBus("http://localhost:8080/eventbus?token=" + token);
and here's how you can get it on a server:
String token = ctx.request().getParam("token");
Send authorization message after connecting to the server. It can be some JSON object, which contains token field.
I think, 1st option is enough, however, 2nd one can be harder to implement in terms of Event Bus and SockJS.
Since sending Authorization header is not possible, attaching a token query parameter (as described by #berserkk) is the way to go.
However, in some circumstances, it may be undesirable to send your main login token in plain text as a query parameter because it is more opaque than using a header and will end up being logged whoknowswhere. If this raises security concerns for you, an alternative is to use a secondary JWT token just for the web socket stuff.
Create a REST endpoint for generating this JWT, which can of course only be accessed by users authenticated with your primary login token (transmitted via header). The web socket JWT can be configured differently than your login token, e.g. with a shorter timeout, so it's safer to send around as query param of your upgrade request.
Create a separate JwtAuthHandler for the same route you register the SockJS eventbusHandler on. Make sure your auth handler is registered first, so you can check the web socket token against your database (the JWT should be somehow linked to your user in the backend).
I think best way to secure a web-socket is using CORS check
Cross Origin Resource Sharing is a safe mechanism for allowing resources to be requested
router.route().handler(CorsHandler.create(your host origin path).allowCredentials(true));
We can add more layer of security also using sockjs :
Allow events for the designated addresses in/out of the event bus bridge
BridgeOptions opts = new BridgeOptions()
.addInboundPermitted(new PermittedOptions().setAddressRegex(Constants.INBOUND_REGEXP));

Not able to Send Message specific to User using Spring Websocket STOMP

I am trying to create a chat app using spring websocket and stomp.
I am using Spring 4.1.1,Stomp.js, ActiveMQ 5.9
In this user can send message to each of his/her friends, who are also logged in, by logging into the app.
For sending message to particular user I take following steps:
1) User logs in
2) User subscribes to "/user/queue/messaging" destination.
This will be used to send private messages of users to each other.
3) when user wants to send a message he sends it to destination :
/user/{user_id}/queue/messaging where user_id is recipients user id.
I am trying to send this from client using STOMP.js send method.
4) Expected behaviour : now if recipient is logged in and his session id, for example, is DFT0GH then the message in step e should be delivered to Queue destination with name messaging-userDFT0GH. Instead of this it is delivered to the same user's queue destination who sent it.
Please find my example scenario :
1) User John logs in .
He subscribes to /user/queue/messaging
His user id is john
His session id is ABCD01
Queue is created with name on activemq broker as
messaging- userABCD01
2) User Alice logs in .
She subscribes to /user/queue/messaging
His user id is alice
Her session id is XYZ01
Queue is created with name on activemq broker as messaging- userXYZ01
3) user John sends a message through STOMP.js send method to Alice
using destination as "/user/alice/queue/messaging"
4) now instead of delivering the message to queue
messaging- UserXYZ01 it gets delivered to John's queue destination i.e
messaging- userABCD01. Why is it so?
When i debugged this , I found following line in method
private DestinationInfo parseUserDestination(Message message) of DefaultUserDestinationResolver class :
if (SimpMessageType.MESSAGE.equals(messageType)) {
........
sessionIds = (sessionId != null ?
Collections.singleton(sessionId) : this.userSessionRegistry.getSessionIds(user));
}
In this sessionId is logged in user's (Principal) session id which is not null as user is logged in and so his sessionIds is returned and message is delivered to his queue even if intended recipient user is different.
When i check usersessionregistry's sessionIds collection I find an entry [alice]:XYZ01.
Shouldn't above line return session id if the user instead of logged in user's session to identify destination queue.?
Sorry I am trying this for the first time. So Please let me know if I miss anything here and of there is
1) any way to satisfy my use case
2) or my use case itself is invalid.
Thanks in advance.
Just so this question stays out of the unanswered list - this is indeed a bug and you raised it as SPR-12444.
This will be fixed in Spring Framework 4.1.3.
As a side note, I'd like to point out that if you're deploying your application with multiple instances, session registries are not shared between instances by default - so this will cause issues when sending a message from a alice (with a session to server #1) to bob (session to server #2).

Spring 4 STOMP not sending queue-suffix in header on connect

I wrote a small project that uses Spring MVC 4 with Websockets and RabbitMQ as the broker.
I am trying to send back to a single user (convertAndSendToUser) but I can't seem to have it working. Here is what the application does:
authenticate to Spring Security over HTTP. The authentication is over AJAX and the backend assigns a UUID as the username to every new connection. There might be multiple clients with the same username so I would like to be able to target individual browsers although they may be logged in with the same username
after authenticating over AJAX the webapp connects to the APIs using STOMP over Websockets.
These are the headers that get send back in the CONNECT FRAME
body: ""
command: "CONNECTED"
headers: Object
heart-beat: "0,0"
server: "RabbitMQ/3.3.1"
session: "session-OGzAN6T8Y0X9ft3Jq04fiQ"
user-name: "88dc9424-72e3-4814-be8c-e31dbf89b521"
version: "1.1"
As you can see there is no frame-suffix or session-id field.
When I use convertAndSendToUser from the backend, the response is send to a unique queue like: /exchange/jobs-userh6g_48h9. The queue name is /exchange/jobs but Spring automatically attaches the suffix -userh6g_48h9. The username is "user" and the sessionId is "h6g_48h9" This is fine and I want this behavior but the problem is that the client (webapp) doesn't get this session id in the CONNECT frame and there is no way for it to actually subscribe to that unique queue.
How to solve this? I want the queue-suffix/session id sent back to me on connect so that the client can get this suffix and subscribe to his unique queues. Currently no messages come back from the server because of this lack of information.
Correct me if I'm wrong, but due to the UserDestinationMessageHandler, clients should be able to subscribe to /user/exchange/jobs and that message handler will correct the queue name for you.
As a side note, you should also consider adding a configuration class extending AbstractSecurityWebSocketMessageBrokerConfigurer similar to the following:
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.antMatchers(SimpMessageType.MESSAGE, "/topic/**", "/queue/**").denyAll() // Prevent users sending messages directly to topics and queues
.antMatchers(SimpMessageType.SUBSCRIBE, "/queue/**/*-user*", "/topic/**/*-user*").denyAll() // Prevent users from subscriptions to private queues
.antMatchers("/user/queue/errors").permitAll()
.anyMessage().hasRole("ANY_OPTIONAL_ROLE_ETC")
;
}
}
This will prevent "enterprising" users from subscribing to things that they shouldn't.

JavaMailSender SMTP Bounce back - Different domain email address

I'm using the Spring java mailer class to send email messages to my users:
org.springframework.mail.javamail.JavaMailSenderImpl version 1.4 using Spring framework 3.0.7.RELEASE.
I want to set the bounce back message for a failed email to go to my user's email address that doesn't have the same domain as my smtp server. Does anyone know a way to accomplish this?
For example:
My system sends an email to email-does-not-exist#gmail.com. My smtp server is configured to have a domain somebusiness.com. Upon failure, send the bounceback to my user: test.user#gmail.com.
I read the following article several times:
Specifying the bounce-back address for email
I tried to use their method of setting the mail.smtp.from property but it won't send any emails at all (not even counting bounceback attempts from invalid emails yet).
Properties p = new Properties();
p.put("mail.smtp.from", "test.user#gmail.com"); //If I comment this out, it sends emails again
mailSender.setJavaMailProperties(p);
Session session = Session.getDefaultInstance(p, null);
MimeMessage mimeMessage = new MimeMessage(session);
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
false, "utf-8");
mimeMessage.setContent(emailBody, "text/html");
helper.setTo(toAddress);
helper.setSubject(subject);
helper.setFrom(fromAddress);
mailSender.send(mimeMessage);
Anyone have an idea of why? The obvious answer seems like the smtp server we are using is blocking it but I was hoping for potential other ideas.
I'm having a similar problem. I don't have a solution yet, but at the moment I am considering replacing Spring's mail package with org.apache.commons.mail because it has a simple setBounceAddress(emailAddressString) method.
See the very end section "Handling Bounced Messages" of the user guide:
http://commons.apache.org/proper/commons-email//userguide.html
And the API docs:
http://commons.apache.org/proper/commons-email//apidocs/org/apache/commons/mail/Email.html#setBounceAddress(java.lang.String)
I just checked how the Apache Commons Mail implements it's bounce functionality and it actually just sets the from address. It means that you can do the same in Spring Mail by setFrom(...) on a org.springframework.mail.javamail.MimeMessageHelper class.
Source code snippet from org.apache.commons.mail.Email class:
if (this.bounceAddress != null) {
properties.setProperty(MAIL_SMTP_FROM, this.bounceAddress);
}
See it in the sources:
http://grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/commons-email/1.2/org/apache/commons/mail/Email.java#539

Resources