Persisting Keycloak Sessions Across Restarts

en

Update (2024-06-10)

Starting with version 25, Keycloak comes with a new persistent-user-session feature that stores session data in the database in addition to in memory. I recommend enabling that feature using features=persistent-user-session in keycloak.conf rather than using persistent Infinispan caches.


In my hackerspace we're running a Keycloak SSO service to which most of our other member services are hooked up to. Keycloak is set up as a single instance, so by default all user sessions are lost in the case of a restart, and everybody has do sign in again.

At the same time, we're doing automated upgrades every time a new Keycloak version is released, so restarts (with session loss) occur quite often, and users start complaining.

Keycloak does offer the option to persist user sessions to disk, but all guides I could find online were either outdated and still referred to the old Wildfly version of Keycloak, or were incomplete and/or simply broken. The closest solution to the actual truth was a post in the Keycloak Discourse forum, but even that one didn't work out of the box for me. So here's the config with which it got it to work with Keycloak Quarkus 24.

Keycloak Configuration Changes

First of all, the Infinispan caching of Keycloak must be enabled, and pointed to the XML config file bundled with Keycloak. For this, the following lines need to be added to conf/keycloak.conf:

# Enable Infinispan Cache
cache=ispn
# Load Infinispan config from cache-ispn.xml rather than builtin default
cache-config-file=cache-ispn.xml
# Switch the JGroups default stack to TCP (more on that later)
cache-stack=tcp

Next, a few changes to conf/cache-ispn.xml are required. First of all, since this is a single-node instance, we don't want JGroups to probe for other cluster members via multicast pings, therefore we override the default tcp stack with a static node list containing only localhost. For this, add the following config snippet as the first child element of the root <infinispan> node:

<jgroups>
    <stack name="tcp-static" extends="tcp">
        <TCP bind_addr="127.0.0.1" bind_port="7800" />
        <TCPPING initial_hosts="127.0.0.1[7800]"
                 port_range="0"
                 stack.combine="REPLACE"
                 stack.position="MPING"
                 max_dynamic_hosts="0" />
    </stack>
</jgroups>

This stack named tcp-static needs to be enabled by overriding the stack name in the infinispan.cache-container.transport element:

<transport lock-timeout="60000" stack="tcp-static" />

Now, for session persistence: First we need to tell Infinispan where it can store its global state. Add the following snippet as the first child element of the infinispan.cache-container node (you can choose your own path of course):

<global-state>
    <persistent-location path="/opt/keycloak/cache/" />
</global-state>

Finally, every single cache defined as a distributed-cache element in the XML file needs to get a persistence section added to it, telling it where to persist its data. Here for example the config of the authenticationSessions cache:

<distributed-cache name="authenticationSessions" owners="2">
    <expiration lifespan="-1"/>
    <persistence passivation="true">
        <file-store shared="false" />
    </persistence>
</distributed-cache>

Repeat this for every single distributed-cache entry in the XML file. The caches will be placed in a folder named after the cache name inside the path specified in the global-state section.

For Keycloak 24, this results in this config file. Note that this config may require changes for major version upgrades.

Final Steps

It appears that Quarkus does not pick up changes to the cache-ispn.xml file reliably during auto-build. An explicit build was required after each config change and before restarting Keycloak:

keycloak:~$ bin/kc.sh build

To test whether session persistence is working properly, I followed this approach:

  1. Pick two Keycloak clients to test with, and make sure you're signed out of both of them. Also, make sure you don't have an active Keycloak session.
  2. Sign into the first client via SSO. You should get redirected to Keycloak's login interface. Complete the login and return to the first client.
  3. Restart Keycloak and wait for it to have started fully.
  4. Sign into the second client via SSO. Login should complete automatically, without any password prompt from Keycloak.