I know how to disconnect Sessions from Client Side, but I couldn't find a way to disconnect my session from my Spring Boot Backend. I've already seen the following post:
Disconnect client session from Spring websocket stomp server
This would kinda adress my problem, but I thought maybe there is a much more elegant or easier way to solve my problem, since the above mentioned post is already 8 years old. And still i couldn't get it to work as expected.
Trying to sketch my exact problem:
JS-Client-Side looks like this(pseudo code):
![creates a simple request and sends it to my Spring Backend]
function subscribeToUser(){
request = {};
request.type = "USER";
var user = {};
user.userId = userId;
user.email = email;
request.user = user;
send(request);
}
Server-Side:
Here I detect a Subscribe Event, extract the destination, and check if it is valid. If there is some problem
with the destination I want my server to disconnect from that client.(This should happen in line 122)
#EventListener
private void handleSessionSubscribe(SessionSubscribeEvent event){
String destination =
event.getMessage().getHeaders().get("simpDestination").toString();
Principal p = canSubscribeToThatEndpoint(destination,
event.getUser());
}
private Principal canSubscribeToThatEndpoint(String dest, Principal
user){
if(dest.containt("some invalid thing")){
//close the session from server-side
}else return user;
}
I already tried to follow the mentioned StackOverflow Post but I couldn't get it to run. Also another method would be to send a message from the backend and trigger a disconnect Event in JS. But I think it would be convient(if there is a way) to access current client sessions in Backend and disconnect from them if needed.
Related
I am using Springwebsockets with the STOMP protocol over Websockets, and i am using the in memory borker. I want to send messages to specific users.
On the client side i subscribe to a private topic:
stompClient.subscribe('/user/topic/private', function(greeting){
});
And i send the message like this:
stompClient.send("/user/"+ user +"/topic/private", {}, message);
This all works fine and i don't have to do anything on the server. But when i log in twice with the same user (eg different browsers, one desktop and one mobile), it doesn't work like i expected. I would expect that the private message ends up in both the browser. However it only seems to end up in the current browser.
Is there any better way to do this kind of private messaging? Or did i misunderstand how the private channel works?
You can try this:
#SendToUser(value = "your destination",broadcast = true)
public String handleException(Message message) {
//your logic goes here
return message; // this will be sent to user
}
I am using ServiceStack v4 with custom Authentication. This is setup and working correctly. I can call the /auth service and get a returned AuthorizationResponse with unique SessionId.
I also have swagger-ui plugin setup. Using it, I can authenticate via /auth and then call one of my other services which require authentication without issue.
Now, from a secondary MVC application using the c# JsonServiceClient I can again successfully make a call to /auth and then secured services using the same client object. However, if I dispose of that client (after saving the unique sessionId to a cookie), then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401. If I call a non-secure service, but then try to access the unique user session, it returns a new session.
If I look at the request headers in that service, the cookie or Header is clearly set with the sessionId. The sessionId also exists in the sessionCache. The problem seems to be that the code which tries to get the session from the request isn't finding it.
To be more specific, it appears that ServiceExtensions.GetSessionId is looking at the HostContext and not the calling Request. I'm not sure why. Perhaps I misunderstand something along the way here.
If I directly try and fetch my expected session with the following code it's found without issue.
var req = base.Request;
var sessionId = req.GetHeader("X-" + SessionFeature.PermanentSessionId);
var sessionKey = SessionFeature.GetSessionKey(sessionId);
var session = (sessionKey != null ? Cache.Get<IAuthSession>(sessionKey) : null)?? SessionFeature.CreateNewSession(req, sessionId);
So, am I missing something obvious here? Or maybe not so obvious in creating my secondary client?
Sample code of client calls
Here is my authorization code. It's contained in a Controller class. This is just the relevant parts.
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
try
{
loginResult = client.Post(new Authenticate()
{
UserName = model.Email,
Password = model.Password,
RememberMe = model.RememberMe
});
Response.SetCookie(new HttpCookie(SessionFeature.PermanentSessionId, loginResult.SessionId));
return true;
}
}
Here is my secondary client setup and service call, contained in it's own controller class in another area of the MVC application
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
var cCookie = HttpContext.Request.Cookies.Get(SessionFeature.PermanentSessionId);
if (cCookie != null)
{
client.Headers.Add("X-" + SessionFeature.PermanentSessionId, cCookie.Value);
client.Headers.Add("X-" + SessionFeature.SessionOptionsKey, "perm");
}
response = client.Get(new SubscriptionStatusRequest());
}
Additional Update
During the Authenticate process the following function is called from HttpRequestExtensions with the name = SessionFeature.PermanentSessionId
public static class HttpRequestExtensions
{
/// <summary>
/// Gets string value from Items[name] then Cookies[name] if exists.
/// Useful when *first* setting the users response cookie in the request filter.
/// To access the value for this initial request you need to set it in Items[].
/// </summary>
/// <returns>string value or null if it doesn't exist</returns>
public static string GetItemOrCookie(this IRequest httpReq, string name)
{
object value;
if (httpReq.Items.TryGetValue(name, out value)) return value.ToString();
Cookie cookie;
if (httpReq.Cookies.TryGetValue(name, out cookie)) return cookie.Value;
return null;
}
Now what occurs is the httpReq.Items contains a SessionFeature.PermanentSessionId value, but I have no clue why and where this gets set. I don't even understand at this point what the Items container is on the IRequest. The code thus never gets to the functionality to check my cookies or headers
The Session wiki describes the different cookies used by ServiceStack Session.
If the client wants to use a Permanent SessionId (i.e. ss-pid), it also needs to send a ss-opt=perm Cookie to indicate it wants to use the permanent Session. This Cookie is automatically set when authenticating with the RememberMe=true option during Authentication.
There was an issue in the Session RequestFilter that was used to ensure Session Id's were attached to the current request weren't using the public IRequest.GetPermanentSessionId() API's which also looks for SessionIds in the HTTP Headers. This has been resolved with this commit which now lets you make Session requests using HTTP Headers, e.g:
//First Authenticate to setup an Authenticated Session with the Server
var client = new JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "user",
Password = "p#55word",
RememberMe = true,
});
//Use new Client instance without Session Cookies populated
var clientWithHeaders = new JsonServiceClient(BaseUrl);
clientWithHeaders.Headers["X-ss-pid"] = authResponse.SessionId;
clientWithHeaders.Headers["X-ss-opt"] = "perm";
var response = clientWithHeaders.Send(new AuthOnly()); //success
This fix is available from v4.0.37+ that's now available on MyGet.
However, if I dispose of that client (after saving the unique sessionId to a cookie)
If the client is disposed where is the cookie you are saving the sessionId located? This answer might provide some additional information.
then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401
If you store/save a valid sessionId as a string you should be able to supply it within a CookieContainer of a new client (given the sessionId is still authenticated). I know you said you tried adding the sessionId as a Cookie but I don't a see sample within your question using the CookieContainer so it should look something like...
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
var cCookieId = savedCookieId; //a string that I believe you saved from a successfully authenticated client that is now disposed
if (cCookieId != null)
{
var cookie = new Cookie(SessionFeature.PermanentSessionId, cCookieId);
//cookie.Domian = "somedomain.com" //you will probably need to supply this as well
client.CookieContainer.Add(cookie)
}
response = client.Get(new SubscriptionStatusRequest());
}
I am trying to get notified when an user subscribes to a stomp user destination using #SubscribeMapping annotation. My ideia is to send some initialization data to when it joins.
Although i am not being able to get this working:
Javascript:
stompClient.subscribe('/user/monitor', function(msg) {
console.log(msg);
});
Java side:
#SubscribeMapping("/monitor-user{clientId}")
public void init(#DestinationVariable("clientId") String clientId) {
messagingTemplate.convertAndSend("/user/" + clientId + "/monitor", getOps());
}
I tried many mapping combinations such as "/user/monitor-{clientId}", "/user/monitor" (Removing clientId), no success at all.
What is the expected mapping value so this get called?
Thank you!
Since the client subscribes to the "/user/monitor" queue, the server #SubscribeMapping annotation must be to "/user/monitor" and not to "/user/monitor{something}".
The server can understand what client is depending on your authentication scheme. If you use a sockjs websocket you can use HTTP authentication, and hence leverage Spring Security, and add a "Principal" parameter to your function that will hold user information:
#SubscribeMapping("/monitor")
public void init(Principal p) {
String user = p.getName();
messagingTemplate.convertAndSendToUser(user, "/monitor", getOps());
}
If you don't use http authentication you may send the server the user info, for example adding a custom STOMP header that can be accessed from the server using a SimpMessageHeaderAccessor:
#SubscribeMapping("/monitor")
public void init(SimpMessageHeaderAccessor accessor) {
String user = accessor.getFirstNativeHeader("userid");
messagingTemplate.convertAndSendToUser(user, "/monitor", getOps());
}
Another way could be to subscribe to a different queue name which contains the user information (and this maybe was your proposal). The client must use a code similar to:
stompClient.subscribe('/topic/byUser/'+userId, function(msg) {
console.log(msg);
});
so that the server can access it this way:
#SubscribeMapping("/byUser/{userId}")
public void init(#DestinationVariable String userId) {
messagingTemplate.convertAndSendToUser(userId, "/monitor", getOps());
}
but in this case keep in mind that that queue is public and hence other clients can access their messages if they knows other client names.
i know there is a lot of questions on this already but I still didn't seem to find a definitive answer. What i'm looking to do is have users be remembered after they login for say 2 weeks or until they log out. Below is what I think should be happening and I was wondering if anyone with a bit more experience could tell me if i'm right or wrong.
User logs in for the first time. An RPC call to the server returns a 'UserInfo' object which includes with it a new sessionID. Aka on the server this happens and user is returned:
user.setSessionId(getThreadLocalRequest().getSession().getId());
Now after user is returned we must create Cookies to store the client side data. Am i correct in saying we need a Cookie to identify the current user and another for the sessionID:
final long DURATION = 1000 * 60 * 60 * 24 * 14; //duration remembering login - 2 weeks
Date expires = new Date(System.currentTimeMillis() + DURATION);
String sessionID = user.getSessionId();
String username = user.getUsername();
Cookies.setCookie("sessionID", sessionID, expires, null, "/", false);
Cookies.setCookie("username", username, expires, null, "/", false);
Now when the user comes back to the app at a later date we check the cookies and (provided they exists and have not been deleted for some reason such as an explicit log out from the user) use their values to check session validity via RPC to the server:
String sessionID = Cookies.getCookie("sessionID");
String username = Cookies.getCookie("username");
if ( sessionID != null && username != null){
AsyncCallback<UserInfo> callBack = new AsyncCallback<UserInfo>(){
#Override
public void onFailure(Throwable caught) {
Window.alert("Error connecting to server.");
}
#Override
public void onSuccess(Boolean sessionValid) {
if (sessionValid)
loadInitialInterfaceForUser("username");
else
loadLoginInterface();
}
};
loginSvc.checkSessionValidity(sessionID,username, callBack);
}
else
loadLoginInterface();
Now, assuming what I have done so far is correct (which is a bit of a long shot :P) my real question is what exactly should I check at the server side checkSessionValidity(sessionID,username)?
Is it simply a case of fetching the user that I have stored serverside and comparing sessionID with the sessionID I have associated with user? Do I also check it hasn't expired?
I know this is a longwinded and perhaps not very well worded question... I'm struggling to get my head round it so any and all help is very welcome!
Cheers, Steve
Yes,that is a key thing to do.
Here is some interesting point discussed on that (Storing session id and username in DB)
Have a look on this (ofcourse you can check them in impl class instead of servlet)
how to check if a sessionId is valid in a servlet (java).
And here is an excellent example of Session Management in GWT
http://varuntayur.wordpress.com/2012/01/25/session-management-in-gwt
Read this also
question on GWT, Cookies and webpage directing
Take a look at the following link.
Cannot use same cookie for multiple GWT applications
This might solve your problem.
I am developing a Restful Web Service using Jersey between my Android, iPhone apps and MySQL. I also use Hibernate to map the data to the database.
I have a sessionId (key). it is generated when user Login to the system.
In User class:
public Session daoCreateSession() {
if (session == null) {
session = new Session(this);
} else {
session.daoUpdate();
}
return session;
}
In Session Class:
Session(User user) {
this.key = UUID.randomUUID().toString();
this.user = user;
this.date = new Date();
}
void daoUpdate() {
this.key = UUID.randomUUID().toString();
this.date = new Date();
}
When user Sign in to the system successfully, I send this sessionId to the Mobile app client. Then when I want to get some information from database based on the logged in user, I check this Session key as authentication in the REST Services for every request.
For example for the list of project that user is involved in, I use client.GET(SERVER_ADDRESS/project/get/{SessionID})
insetead of client.GET(SERVER_ADDRESS/project/get/{username}).
And if it is not a valid session key, I'll send back to the client a 403 forbidden code.
You can also take a look here
The thing is I am not sure about my approach. what do you think about cons in this approach considering for Jersey and a mobile app?
I still don't know if the Session key approach is a good idea in my case.
If you want to use SessionId then it should have a validation time, like this:
private static final int MINUTES = 90;
public boolean isValid() {
return System.currentTimeMillis() - date.getTime() < 1000 * 60 * MINUTES;
}
This is a solved problem - servlet containers like Tomcat already do session management, and can distribute session state to other containers in the cluster either by broadcasting over TCP, or by using a shared data source like memcache.
I'd suggest reading up on what's already available, rather than inadvertently reinventing the wheel. Additionally, this is going to become an incredibly hot table table if your application proves popular. How will you clear out old session IDs?