Spring Webflex: Push Server Sent Event to Specific Users - spring

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());
}

Related

How to reach previously read emails by using Spring Integration?

I am developing a dynamic and multiple IMAP channel listener application. For the purpose of effectiveness, I am not downloading the attachments inside mails, just getting the texts inside them. Also I am developing an endpoint to access that previously arrived mails and download & return that attachment in order not to download every attachment. So basically I am trying to download attachments only if there is a demand.
I am using ImapIdleChannelAdapter to listen mails inside integration flow. Here is my flow,
public ImapIdleChannelAdapter mailAdapter(ImapMailReceiver receiver) {
ImapIdleChannelAdapter imapAdapter = new ImapIdleChannelAdapter(receiver);
imapAdapter.setAutoStartup(true);
return imapAdapter;
}
public IntegrationFlow createMailFlow(GmailRecieverRequirements requirements, String clientID) {
return IntegrationFlow.from(
mailAdapter(gmailMailReceiver(requirements)))
.handle(getMailHandler())
.get();
}
My question is, how can I access those previously read mails in different time? I know Java Mail has Folder - UID structure to access mails via UIDs. Here is the link. However, I do not want to use javaMail inside my flow to save the UID. Is there any chance that I could reach UID of the mail inside the flow by Spring Integration? I am open to any other solution.
Thanks in advance
This is indeed low-level mail API functionality. Not sure what you mean with your do not want to use javaMail inside my flow, but we only can do that on demand fetching if we know the folder and message UID and with that respective API of the IMAPFolder:
/**
* Get the Message corresponding to the given UID. If no such
* message exists, <code>null</code> is returned.
*
* #param uid UID for the desired message
* #return the Message object. <code>null</code> is returned
* if no message corresponding to this UID is obtained.
* #exception MessagingException for failures
*/
public Message getMessageByUID(long uid) throws MessagingException;
The ImapIdleChannelAdapter can produce raw jakarta.mail.Message to let you to obtain its UID via UIDFolder.getUID(Message message) API.

How client and server mapping message on Spring boot websocket

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.

How to avoid 'Choose Account' screen with Google Calendar API?

Our app is importing the next 1000 events from a user's Google calendar API. We ran into the problem where nginx would timeout. To get around this I'm putting the pagination data into a session variable and making separate HTTP requests to the API. This works except for one problem: every time we make a new HTTP request the API asks the user to choose which account they want to use (one user with multiple gmail accounts). I would have thought that the pagination data would include account selection but this is apparently not the case. How can I programmatically select the email account within the HTTP request?
You can store it once
public static void setmCredential(GoogleAccountCredential _mCredential) {
mCredential = _mCredential;
mService = new com.google.api.services.calendar.Calendar.Builder(
transport, jsonFactory, mCredential)
.setApplicationName("YourApplicationName")
.build();
}
And then when caliing pass it like this
new MakeRequestTask(AccountCredential.mService).execute();

Why the session attribute is coming as null

A HTML5 UI is connected to the backend (REST Jersey to business logic to Hibernate and DB). I need to create and maintain a session for each user login until the user logs out.
I am clueless on how to approach this problem.
I followed this approach
Initially when the User is successfully logs in , i am setting attribute under session as shown below
HttpSession session = request.getSession(true);
session.setAttribute("islogged", "islogged");
String value = (String)session.getAttribute("islogged");
System.out.println("****************** The User Logge in Value"+value);
Later in a different page i am checking if the user is logged in or not this way
public String checkIfUserLoggedIn() throws JSONException,ClassNotFoundException, SQLException
{
HttpSession session = request.getSession();
String value = (String)session.getAttribute("islogged");
if(value==null)
{
// always its coming here only
}
}
I agree with francesco foresti, please do not rely on HTTP session without Auth. this is unsafe, and quite dangerous for your app.
Have you been implementing a specific session mecanism ?
If not, jersey as it is will not store session data as it. Every call that you will make will give you a session id that is different from yours.
You have to make authentication & use the auth token in order to identify you session.
use JAX-RS
Please do use an auth mecanism as defined : https://jersey.java.net/documentation/latest/security.html
#Path("authentication")
#Singleton
public static class MyResource {
// Jersey will inject proxy of Security Context
#Context
SecurityContext securityContext;
#GET
public String getUserPrincipal() {
return securityContext.getUserPrincipal().getName();
}
}
or use another framework : Spring, Shiro.... etc.
I really prefer that solution, since another framework will implement a lot of stuff for you. You gain a lot of time doing so.
Please take a look to official jersey doc: https://jersey.java.net/documentation/latest/index.html
I wouldn't rely on the http session. My approach would be to put an "Authorization" field in the header of the response that the server returns when the user logs in, and ask the user to put the very same header in each suqsequent call. In this header you put the informations that help the server find the identity of the user
(take a look at what twitter does as an example : https://dev.twitter.com/oauth/overview/authorizing-requests). The server could save the informations about the logged in user in the database, or You could create a Map in a Singleton that would serve as the "authorization gatekeeper" for your services.

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.

Resources