-
Notifications
You must be signed in to change notification settings - Fork 535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Session is not removed from the ClusteredSessionStore
, if the session is destroyed
#2674
Comments
Thanks for the report @fbuetler We have Any idea about why this test doesn't capture the problem? |
Thank you for the rapid response and for pointing me to the test case. The In the end, both |
I apologize, I overlooked, that you also mentioned vertx-infinispan. AFAICT, to hit the problematic code in |
The reason for the session never actually being stored in infinispan, is because a session is only flushed at the end of a request being processed. As we create and destroy the session in one request, it is never actually added to the session store, but only removed, as observed in the previous comment. |
Maybe something like this would be more appropriate: @Test
public void testDestroySession() throws Exception {
// given
router.route().handler(SessionHandler.create(store));
final AtomicReference<String> sid = new AtomicReference<>();
final AtomicInteger requestCount = new AtomicInteger();
router.route().handler(rc -> {
Session sess = rc.session();
assertNotNull(sess);
assertTrue(System.currentTimeMillis() - sess.lastAccessed() < 500);
assertNotNull(sess.id());
switch (requestCount.get()) {
case 0:
sid.set(sess.id());
sess.put("foo", "bar");
break;
case 1:
assertEquals(sid.get(), sess.id());
// Destroy session in a follow-up request, so that it has been flushed to
// the session store
sess.destroy();
break;
case 2:
assertFalse(sid.get().equals(sess.id())); // New session
assertNull(sess.get("foo"));
sid.set(sess.id());
sess.destroy(); // Destroy session in the same request as it is created
break;
}
requestCount.incrementAndGet();
rc.response().end();
});
// when
final AtomicReference<String> sessionCookie = new AtomicReference<>();
testRequest(HttpMethod.GET, "/", null, resp -> {
assertTrue(resp.headers().getAll("set-cookie").size() == 1); // expect new session cookie only
String setCookie = resp.headers().get("set-cookie");
assertNotNull(setCookie);
sessionCookie.set(setCookie.split(";")[0]);
}, 200, "OK", null);
testRequest(HttpMethod.GET, "/", req -> req.putHeader("cookie", sessionCookie.get()), resp -> {
assertTrue(resp.headers().getAll("set-cookie").size() == 1); // expect expired session cookie only
}, 200, "OK", null);
testRequest(HttpMethod.GET, "/", req -> req.putHeader("cookie", sessionCookie.get()), null, 200, "OK", null);
// then
Thread.sleep(500); // Needed because session.destroy is async
CountDownLatch latch = new CountDownLatch(1);
store.get(sid.get(), onSuccess(res -> {
assertNull(res);
latch.countDown();
}));
awaitLatch(latch);
} What do you think @tsegismont ? (This does not test original issue, that we reported, but makes sure the session is actually flushed to the session store) |
Further, I am looking into a way to use a |
I created a test case that replicates our problem. It is failing in We think the main issue is that In this scenario, an out-of-band In general, we think the @Test
public void testOutOfBandDestroyClusteredSession() throws Exception {
// given
router.route().handler(SessionHandler.create(store));
final AtomicReference<String> sid = new AtomicReference<>();
final AtomicInteger requestCount = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(1);
router.route().handler(rc -> {
Session sess = rc.session();
assertNotNull(sess);
// assertTrue(System.currentTimeMillis() - sess.lastAccessed() < 500);
assertNotNull(sess.id());
switch (requestCount.get()) {
case 0:
sid.set(sess.id());
break;
case 1:
// out of band session.destroy
store.get(sid.get(), onSuccess(s -> {
assertNotNull(s);
s.destroy();
store.put(s, onSuccess(v -> latch.countDown())); // manually flush the session
}));
break;
case 2:
// then
assertTrue(sess.isDestroyed());
assertTrue(String.format("should have new session id: %s, actual %s", sid.get(), sess.id()),
sid.get().equals(sess.id())); // Still the old session
sess.data(); // Session is lazily regenerated on data access
assertTrue(String.format("should have new session id: %s, actual %s", sid.get(), sess.id()),
!sid.get().equals(sess.id())); // New session
break;
}
requestCount.incrementAndGet();
rc.response().end();
});
// when
final AtomicReference<String> sessionCookie = new AtomicReference<>();
testRequest(HttpMethod.GET, "/", null, resp -> {
assertTrue(resp.headers().getAll("set-cookie").size() == 1); // expect new session cookie only
String setCookie = resp.headers().get("set-cookie");
assertNotNull(setCookie);
sessionCookie.set(setCookie.split(";")[0]);
}, 200, "OK", null);
Thread.sleep(500); // Needed because session.flush is async
testRequest(HttpMethod.GET, "/", null, null, 200, "OK", null);
awaitLatch(latch);
testRequest(HttpMethod.GET, "/", req -> req.putHeader("cookie", sessionCookie.get()), null, 200, "OK", null);
} |
…been flushed to the session store at least once (vert-x3#2674)
…been flushed to the session store at least once (vert-x3#2674) Making sure that the session has been flushed to the session store, ensures that the serialization/deserialization process of the stored object has been tested as well.
The more I think about it, the less I think the whole session state (like One could argue that the Conclusion: To implement out-of-band session destruction, one has to use Nevertheless, these test cases still do not cover serialization/deserialization of objects stored in the
|
Thanks for sharing your findings @fbuetler, this is of great value We're focused on getting Vert.x 5 out and I can't look at them right now, I'll come back to you as soon as I can |
Version
4.5.8
Context
We are using the
SessionHandler
together with anAuthenticationHandler
.Expected
If
destroy()
is called on aSession
, we'd expect a session to be deleted from theSessionStore
and further requests with this session are required to be re-authenticated.Actual
In case of a
LocalSessionStore
, it is conforming with our expectation. However, in case ofClusteredSessionStore
, the user-agent is able to do further requests without any re-authentication.We assume the reason behind this, is that the state of the session is not synchronized with the session store. Only the session
id
,timeout
,lastAccessed
,version
anddata
are synchronized, but the state further includesdestroyed
,renewed
,oldId
andcrc
.Source
The functionality to remove the session from the session store in the SessionHandler upon a request with a destroyed session, is therefore not triggered. Therefore, a destroyed session can still be used (as similarly pointed out earlier #329 (comment)).
Our proposed fix is to include the session state in the synchronization.
If the proposed fix is accepted, I volunteer to create a pull request.
The text was updated successfully, but these errors were encountered: