diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java
index af97bb5b23..2061c6e7de 100755
--- a/common/src/main/java/org/keycloak/common/Profile.java
+++ b/common/src/main/java/org/keycloak/common/Profile.java
@@ -106,7 +106,7 @@ public class Profile {
HOSTNAME_V1("Hostname Options V1", Type.DEPRECATED, 1),
HOSTNAME_V2("Hostname Options V2", Type.DEFAULT, 2),
- PERSISTENT_USER_SESSIONS("Persistent online user sessions across restarts and upgrades", Type.EXPERIMENTAL),
+ PERSISTENT_USER_SESSIONS("Persistent online user sessions across restarts and upgrades", Type.PREVIEW),
OID4VC_VCI("Support for the OID4VCI protocol as part of OID4VC.", Type.EXPERIMENTAL),
diff --git a/docs/documentation/release_notes/topics/25_0_0.adoc b/docs/documentation/release_notes/topics/25_0_0.adoc
index e6d8a01506..26bc6880ef 100644
--- a/docs/documentation/release_notes/topics/25_0_0.adoc
+++ b/docs/documentation/release_notes/topics/25_0_0.adoc
@@ -34,6 +34,38 @@ New options are activated by default, so {project_name} will not recognize the o
For information on how to migrate, see the link:{upgradingguide_link}[{upgradingguide_name}].
+= Persistent user sessions
+
+Previous versions of {project_name} stored only offline user and offline client sessions in the databases.
+The new feature `persistent-user-session` stores online user sessions and online client sessions not only in memory, but also in the database.
+This will allow a user to stay logged in even if all instances of {project_name} are restarted or upgraded.
+
+The feature is a preview feature and disabled by default. To use it, add the following to your build command:
+
+----
+bin/kc.sh build --features=persistent-user-session ...
+----
+
+For more details see the https://www.keycloak.org/server/features[Enabling and disabling features] {section}.
+The https://www.keycloak.org/high-availability/concepts-memory-and-cpu-sizing[sizing guide] contains a new paragraph describing the updated resource requirements when this feature is enabled.
+
+NOTE: If this feature is enabled for an existing deployment that is using only the embedded Infinispan for storing sessions, the sessions will not be migrated to the database.
+
+With persistent sessions enabled, the in-memory caches for online user sessions, offline user sessions, online client sessions and offline client sessions are limited to 10000 entries by default which will reduce the overall memory usage of Keycloak for larger installations.
+Items which are evicted from memory will be loaded on-demand from the database when needed.
+To set different sizes for the caches, edit {project_name}'s cache config file to set a `++` for those caches.
+Once this feature is enabled, expect an increased database utilization on each login, logout and refresh token request.
+
+To configure the cache size in an external {jdgserver_name} in a {project_name} multi-site setup, for those caches, consult the updated https://www.keycloak.org/high-availability/deploy-infinispan-kubernetes-crossdc[Deploy Infinispan for HA with the Infinispan Operator] {section}.
+
+With this feature enabled, the options `spi-user-sessions-infinispan-offline-session-cache-entry-lifespan-override` and `spi-user-sessions-infinispan-offline-client-session-cache-entry-lifespan-override` are no longer available which were used to override the time offline sessions are kept in memory.
+
+To log out all online users sessions of a realm with the `persistent-user-session` feature enabled, use the following steps as before:
+
+. Login to the Admin Console.
+. Select the menu entry *Sessions*.
+. Select the action *Sign out all active sessions*.
+
= Cookies updates
== SameSite attribute set for all cookies
diff --git a/docs/documentation/server_admin/topics/sessions/offline.adoc b/docs/documentation/server_admin/topics/sessions/offline.adoc
index df6e2757d5..f7c5b1e969 100644
--- a/docs/documentation/server_admin/topics/sessions/offline.adoc
+++ b/docs/documentation/server_admin/topics/sessions/offline.adoc
@@ -22,7 +22,14 @@ To issue an offline token, users must have the role mapping for the realm-level
Clients can request an offline token by adding the parameter `scope=offline_access` when sending their authorization request to {project_name}. The {project_name} OIDC client adapter automatically adds this parameter when you use it to access your application's secured URL (such as, $$http://localhost:8080/customer-portal/secured?scope=offline_access$$). The Direct Access Grant and Service Accounts support offline tokens if you include `scope=offline_access` in the authentication request body.
-Offline sessions are besides the Infinispan caches stored also in the database. Whenever the {project_name} server is restarted or an offline session is evicted from the Infinispan cache, it is still available in the database. Any following attempt to access the offline session will load the session from the database, and also import it to the Infinispan cache. To reduce memory requirements, we introduced a configuration option to shorten lifespan for imported offline sessions. Such sessions will be evicted from the Infinispan caches after the specified lifespan, but still available in the database. This will lower memory consumption, especially for deployments with a large number of offline sessions. Currently, the offline session lifespan override is disabled by default. To specify the lifespan override for offline user sessions, start {project_name} server with the following parameter:
+Offline sessions are besides the Infinispan caches stored also in the database. Whenever the {project_name} server is restarted or an offline session is evicted from the Infinispan cache, it is still available in the database. Any following attempt to access the offline session will load the session from the database, and also import it to the Infinispan cache.
+
+To reduce memory requirements, we introduced a configuration option to shorten lifespan for imported offline sessions. Such sessions will be evicted from the Infinispan caches after the specified lifespan, but still available in the database. This will lower memory consumption, especially for deployments with a large number of offline sessions. Currently, the offline session lifespan override is disabled by default.
+ifeval::[{project_community}==true]
+This override is only available if the feature `persistent-user-session` is disabled.
+endif::[]
+
+To specify the lifespan override for offline user sessions, start {project_name} server with the following parameter:
[source,bash]
----
@@ -35,3 +42,9 @@ Similarly for offline client sessions:
----
--spi-user-sessions-infinispan-offline-client-session-cache-entry-lifespan-override=
----
+
+ifeval::[{project_community}==true]
+If the feature `persistent-user-session` is enabled, {project_name} will limit its internal cache for offline user and offline client sessions to 10000 entries by default, which will reduce the overall memory usage for offline sessions.
+Items which are evicted from memory will be loaded on-demand from the database when needed.
+To set different sizes for the caches, edit {project_name}'s cache config file to set a `++` for those caches.
+endif::[]
diff --git a/docs/documentation/upgrading/topics/prep_migration.adoc b/docs/documentation/upgrading/topics/prep_migration.adoc
index 2a32389fc0..aec905dd66 100644
--- a/docs/documentation/upgrading/topics/prep_migration.adoc
+++ b/docs/documentation/upgrading/topics/prep_migration.adoc
@@ -5,9 +5,9 @@
Perform the following steps before you upgrade the server.
.Procedure
+. Shutdown {project_name}.
. Back up the old installation, such as configuration, themes, and so on.
. Handle any open transactions and delete the `data/tx-object-store/` transaction directory.
-
. Back up the database using instructions in the documentation for your relational
database.
+
@@ -15,5 +15,15 @@ The database will no longer be compatible with the old server after you upgrade
[WARNING]
====
-After upgrade of {project_name}, except for offline user sessions, user sessions are lost. Users will have to log in again.
+ifeval::[{project_product}==true]
+After the upgrade of {project_name}, except for offline user sessions, user sessions are lost. Users will have to log in again.
+endif::[]
+
+ifeval::[{project_community}==true]
+After the upgrade of {project_name}, only if the feature `persistent-user-session` is enabled, users will still be logged in with their online sessions.
+If it is not enabled, users will have to log in again, except where offline user sessions are used.
+endif::[]
+
+Information about failed logins for the brute force detection and currently ongoing authentication flows is only stored in the internal caches that are cleared when {project_name} is shut down.
+Users currently authenticating, changing their passwords or resetting their password will need to restart the authentication flow once {project_name} is up and running again.
====
diff --git a/docs/guides/high-availability/concepts-memory-and-cpu-sizing.adoc b/docs/guides/high-availability/concepts-memory-and-cpu-sizing.adoc
index d61e5357bf..b7ecca74fa 100644
--- a/docs/guides/high-availability/concepts-memory-and-cpu-sizing.adoc
+++ b/docs/guides/high-availability/concepts-memory-and-cpu-sizing.adoc
@@ -80,6 +80,19 @@ Limits calculated:
+
(1250 MB expected memory usage minus 300 non-heap-usage, divided by 0.7)
+== Persistent user sessions
+
+When using persistent sessions, {project_name} will use less memory as it will keep only a subset of sessions in memory.
+At the same time it will use more CPU resources to communicate with the database, and it will use a lot more CPU and write IO on the database to keep the session information up to date.
+
+A performance test showed the following per 100 requests/second that update the database (login, logout, refresh token), tested up to 300 requests per second:
+
+* 0.25 vCPU on each Pod in a 3-node Keycloak cluster.
+* 0.25 CPU and 800 WriteIOPS on a Aurora PostgreSQL multi-AZ database base on a `db.t4g.large` instance type.
+
+The average latency of the requests increased by 20-40 ms when running on an Aurora PostgreSQL multi-AZ database with a single reader instance on another AZ.
+The latency is expected to be lower when running in a single AZ or non-replicated database.
+
== Reference architecture
The following setup was used to retrieve the settings above to run tests of about 10 minutes for different scenarios:
diff --git a/docs/guides/high-availability/deploy-infinispan-kubernetes-crossdc.adoc b/docs/guides/high-availability/deploy-infinispan-kubernetes-crossdc.adoc
index 3c91ebf3db..7968b83df7 100644
--- a/docs/guides/high-availability/deploy-infinispan-kubernetes-crossdc.adoc
+++ b/docs/guides/high-availability/deploy-infinispan-kubernetes-crossdc.adoc
@@ -143,6 +143,7 @@ The {infinispan-operator-docs}#setting-up-xsite[Setting Up Cross-Site] documenta
+
A basic example is provided in this {section} using the credentials, tokens, and TLS Keystore/Truststore created by the commands from the previous steps.
+
+--
.The `Infinispan` CR for `{site-a}`
[source,yaml]
----
@@ -162,6 +163,17 @@ include::examples/generated/ispn-site-a.yaml[tag=infinispan-crossdc]
<12> The namespace of the {jdgserver_name} cluster from the remote site.
<13> The {ocp} API URL for the remote site.
<14> The secret with the access token to authenticate into the remote site.
+--
++
+When using persistent sessions, limit the cache size limit for `sessions`, `offlineSessions`, `clientSessions`, and `offlineClientSessions` by extending the configuration as follow:
+
+[source,yaml]
+----
+distributedCache:
+ memory:
+ maxCount: 10000
+ # ...
+----
+
For `{site-b}`, the `Infinispan` CR looks similar to the above.
Note the differences in point 4, 11 and 13.
@@ -181,6 +193,7 @@ Cross-site needs to be enabled per cache as documented by {infinispan-xsite-docs
The documentation contains more details about the options used by this {section}.
The following example shows the `Cache` CR for `{site-a}`.
+
+--
.sessions in `{site-a}`
[source,yaml]
----
@@ -190,6 +203,7 @@ include::examples/generated/ispn-site-a.yaml[tag=infinispan-cache-sessions]
Set this for the caches `sessions`, `authenticationSessions`, `offlineSessions`, `clientSessions` and `offlineClientSessions`, and do not set it for all other caches.
<2> The remote site name.
<3> The cross-site communication, in this case, `SYNC`.
+--
+
For `{site-b}`, the `Cache` CR is similar except in point 2.
+
diff --git a/docs/guides/server/caching.adoc b/docs/guides/server/caching.adoc
index 7effe266fb..44b5127480 100644
--- a/docs/guides/server/caching.adoc
+++ b/docs/guides/server/caching.adoc
@@ -107,6 +107,23 @@ to any node without losing their state. However, production-ready deployments sh
to the node where their sessions were initially created. By doing that, you are going to avoid unnecessary state transfer between nodes and improve
CPU, memory, and network utilization.
+ifeval::[{project_community}==true]
+
+The feature `persistent-user-session` stores online user and client sessions also in the database.
+This will allow a user to stay logged in even if all instances of {project_name} are restarted or upgraded.
+
+The feature is disabled by default. To use it, enable the feature:
+
+----
+bin/kc.sh start --features=persistent-user-session ...
+----
+
+With this feature enabled, the in-memory caches for online user sessions and online client sessions are limited to by default 10000 entries which will reduce the overall memory usage of Keycloak for larger installations.
+Items which are evicted from memory will be loaded on-demand from the database when needed.
+To set different sizes for the caches, edit {project_name}'s cache config file to set a `++` for those caches.
+
+endif::[]
+
As an OpenID Connect Provider, the server is also capable of authenticating users and issuing offline tokens. Similarly to regular user and client sessions,
when an offline token is issued by the server upon successful authentication, the server also creates an offline user session and an offline client session. However, due to the nature
of offline tokens, offline sessions are handled differently as they are long-lived and should survive a complete cluster shutdown. Because of that, they are also persisted to the database.
@@ -118,6 +135,14 @@ The following caches are used to store offline sessions:
Upon a cluster restart, offline sessions are lazily loaded from the database and kept in a shared cache using the two caches above.
+ifeval::[{project_community}==true]
+
+With feature `persistent-user-session` enabled, the in-memory caches for offline user sessions and offline client sessions are limited to 10000 entries which will reduce the overall memory usage of Keycloak for larger installations.
+Items which are evicted from memory will be loaded on-demand from the database when needed.
+To set different sizes for the caches, edit {project_name}'s cache config file to set a `++` for those caches.
+
+endif::[]
+
.Password brute force detection
The `loginFailures` distributed cache is used to track data about failed login attempts.
This cache is needed for the Brute Force Protection feature to work in a multi-node {project_name} setup.
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/legacy/infinispan/CacheManagerFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/legacy/infinispan/CacheManagerFactory.java
index 0eb1363d89..1edeaa4620 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/legacy/infinispan/CacheManagerFactory.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/legacy/infinispan/CacheManagerFactory.java
@@ -113,12 +113,17 @@ public class CacheManagerFactory {
}
DISTRIBUTED_REPLICATED_CACHE_NAMES.forEach(cacheName -> {
- if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS) &&
- (cacheName.equals(USER_SESSION_CACHE_NAME) || cacheName.equals(CLIENT_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_USER_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_CLIENT_SESSION_CACHE_NAME))) {
+ if (cacheName.equals(USER_SESSION_CACHE_NAME) || cacheName.equals(CLIENT_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_USER_SESSION_CACHE_NAME) || cacheName.equals(OFFLINE_CLIENT_SESSION_CACHE_NAME)) {
ConfigurationBuilder configurationBuilder = builder.getNamedConfigurationBuilders().get(cacheName);
- if (configurationBuilder.memory().maxSize() == null && configurationBuilder.memory().maxCount() == -1) {
- logger.infof("Persistent user sessions enabled and no memory limit found in configuration. Setting max entries for %s to 10000 entries", cacheName);
- configurationBuilder.memory().maxCount(10000);
+ if (Profile.isFeatureEnabled(Profile.Feature.PERSISTENT_USER_SESSIONS)) {
+ if (configurationBuilder.memory().maxSize() == null && configurationBuilder.memory().maxCount() == -1) {
+ logger.infof("Persistent user sessions enabled and no memory limit found in configuration. Setting max entries for %s to 10000 entries", cacheName);
+ configurationBuilder.memory().maxCount(10000);
+ }
+ } else {
+ if (configurationBuilder.memory().maxCount() != -1) {
+ logger.warnf("Persistent user sessions NOT enabled and memory limit found in configuration for cache %s. This might be a misconfiguration!", cacheName);
+ }
}
}
});