Manually expire web sessions
Instead of relying on the cache implementation to kill web sessions
past the time the admin has configured as allowed, track the end time
when the session was created and initially stored in the cache. If a
valid session key is found in the cache, but it expired before now,
invalidate the key and report it as not found.
Sessions are usually updated in the cache every 50% of the maxAge,
rotating the key and changing the cookie during this time. During one
of these half-expired rotations the session will have a new expire
time set, keeping it alive for a longer time window since the user
is still active.
This saves the cache from needing to update the on access timer on
every request, especially for disk based caches that try to save the
web_sessions across server restarts.
While we are poking at this cache, use String as the cache key rather
than Key now that the H2 based system doesn't need to use the key for
version checks. This simplifies the BloomFilter logic inside of the
disk based cache to be able to hash the token more quickly.
Change-Id: I318e38b2382b7f5ea1188df3ddc7ec6703a5fd3c
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index fb30a4d..7914991 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -170,7 +170,7 @@
/** Set the user account for this current request only. */
public void setUserAccountId(Account.Id id) {
key = new Key("id:" + id);
- val = new Val(id, 0, false, null, "");
+ val = new Val(id, 0, false, null, "", 0);
}
public void logout() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
index 55d0ca5..ee02ef4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
@@ -43,6 +43,7 @@
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.SecureRandom;
+import java.util.concurrent.TimeUnit;
@Singleton
class WebSessionManager {
@@ -104,7 +105,9 @@
final long halfAgeRefresh = sessionMaxAgeMillis >>> 1;
final long minRefresh = MILLISECONDS.convert(1, HOURS);
final long refresh = Math.min(halfAgeRefresh, minRefresh);
- final long refreshCookieAt = now() + refresh;
+ final long now = now();
+ final long refreshCookieAt = now + refresh;
+ final long expiresAt = now + sessionMaxAgeMillis;
if (xsrfToken == null) {
// If we don't yet have a token for this session, establish one.
@@ -115,7 +118,8 @@
xsrfToken = CookieBase64.encode(rnd);
}
- Val val = new Val(who, refreshCookieAt, remember, lastLogin, xsrfToken);
+ Val val = new Val(who, refreshCookieAt, remember,
+ lastLogin, xsrfToken, expiresAt);
self.put(key, val);
return val;
}
@@ -137,7 +141,12 @@
}
Val get(final Key key) {
- return self.get(key);
+ Val val = self.get(key);
+ if (val != null && val.expiresAt <= now()) {
+ self.remove(key);
+ return null;
+ }
+ return val;
}
void destroy(final Key key) {
@@ -184,15 +193,18 @@
private transient boolean persistentCookie;
private transient AccountExternalId.Key externalId;
private transient String xsrfToken;
+ private transient long expiresAt;
Val(final Account.Id accountId, final long refreshCookieAt,
final boolean persistentCookie, final AccountExternalId.Key externalId,
- final String xsrfToken) {
+ final String xsrfToken,
+ final long expiresAt) {
this.accountId = accountId;
this.refreshCookieAt = refreshCookieAt;
this.persistentCookie = persistentCookie;
this.externalId = externalId;
this.xsrfToken = xsrfToken;
+ this.expiresAt = expiresAt;
}
Account.Id getAccountId() {
@@ -233,6 +245,9 @@
writeVarInt32(out, 5);
writeString(out, xsrfToken);
+ writeVarInt32(out, 6);
+ writeFixInt64(out, expiresAt);
+
writeVarInt32(out, 0);
}
@@ -257,10 +272,16 @@
case 5:
xsrfToken = readString(in);
continue;
+ case 6:
+ expiresAt = readFixInt64(in);
+ continue;
default:
throw new IOException("Unknown tag found in object: " + tag);
}
}
+ if (expiresAt == 0) {
+ expiresAt = refreshCookieAt + TimeUnit.HOURS.toMillis(2);
+ }
}
}
}