mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-31 03:49:31 -06:00
Add realm id column to offline_client_session table
Closes #44424 Signed-off-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com> Co-authored-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com>
This commit is contained in:
@@ -65,6 +65,13 @@ It does not affect any explicitly configured clock skew for example in identity
|
||||
|
||||
All the `base` themes are now tagged as **abstract**, and they are not listed in the admin console to be selected (**Realm Settings** -> **Themes** tab). They were always intended to be only extended but not used directly. If you use one of them, it will continue working (or not working) in the same way but cannot be selected using the admin console anymore. Please select one of the available default themes or create your own one.
|
||||
|
||||
=== New database indexes on the `OFFLINE_CLIENT_SESSION` table
|
||||
|
||||
The `OFFLINE_CLIENT_SESSION` table now contains two additional index `IDX_OFFLINE_CSS_BY_CLIENT_AND_REALM` and `IDX_OFFLINE_CSS_BY_USER_SESSION_AND_OFFLINE` to improve performance.
|
||||
|
||||
If the table contains more than 300000 entries, {project_name} will skip the index creation by default during the automatic schema migration and instead log the SQL statement on the console during migration to be applied manually after {project_name}'s startup.
|
||||
See the link:{upgradingguide_link}[{upgradingguide_name}] for details on how to configure a different limit.
|
||||
|
||||
// ------------------------ Deprecated features ------------------------ //
|
||||
== Deprecated features
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2026 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.connections.jpa.updater.liquibase.custom;
|
||||
|
||||
import liquibase.database.core.MSSQLDatabase;
|
||||
import liquibase.database.core.MySQLDatabase;
|
||||
import liquibase.database.core.OracleDatabase;
|
||||
import liquibase.database.core.PostgresDatabase;
|
||||
import liquibase.exception.CustomChangeException;
|
||||
import liquibase.statement.core.RawParameterizedSqlStatement;
|
||||
|
||||
public class JpaUpdate26_6_0_OfflineClientSessionRealm extends CustomKeycloakTask {
|
||||
|
||||
@Override
|
||||
protected void generateStatementsImpl() throws CustomChangeException {
|
||||
String clientSessionTable = getTableName("OFFLINE_CLIENT_SESSION");
|
||||
String userSessionTable = getTableName("OFFLINE_USER_SESSION");
|
||||
|
||||
if (database instanceof PostgresDatabase) {
|
||||
generateUpdateQueryForPostgresSQL(clientSessionTable, userSessionTable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (database instanceof MySQLDatabase) {
|
||||
// and MariaDB
|
||||
generateUpdateQueryForMySQL(clientSessionTable, userSessionTable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (database instanceof MSSQLDatabase) {
|
||||
generateUpdateQueryForMSSQL(clientSessionTable, userSessionTable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (database instanceof OracleDatabase) {
|
||||
generateUpdateQueryForOracle(clientSessionTable, userSessionTable);
|
||||
return;
|
||||
}
|
||||
|
||||
// H2 and others, very slow with O(n^2) complexity
|
||||
// It is standard SQL queries, it *must* be compatible with all vendors (fingers crossed)
|
||||
generateUpdateQueryUsingStandardSQL(clientSessionTable, userSessionTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTaskId() {
|
||||
return "Sets the realm column in offline_client_session";
|
||||
}
|
||||
|
||||
private void generateUpdateQueryUsingStandardSQL(String clientSessionTable, String userSessionTable) {
|
||||
statements.add(new RawParameterizedSqlStatement("""
|
||||
UPDATE %s cs
|
||||
SET cs.realm_id = (
|
||||
SELECT us.realm_id
|
||||
FROM %s us
|
||||
WHERE us.user_session_id = cs.user_session_id AND cs.offline_flag = us.offline_flag
|
||||
)
|
||||
WHERE cs.realm_id IS NULL"""
|
||||
.formatted(clientSessionTable, userSessionTable)));
|
||||
}
|
||||
|
||||
private void generateUpdateQueryForOracle(String clientSessionTable, String userSessionTable) {
|
||||
statements.add(new RawParameterizedSqlStatement("""
|
||||
MERGE INTO %s cs
|
||||
USING %s us
|
||||
ON (cs.user_session_id = us.user_session_id AND cs.offline_flag = us.offline_flag)
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET cs.realm_id = us.realm_id"""
|
||||
.formatted(clientSessionTable, userSessionTable)));
|
||||
}
|
||||
|
||||
private void generateUpdateQueryForMSSQL(String clientSessionTable, String userSessionTable) {
|
||||
statements.add(new RawParameterizedSqlStatement("""
|
||||
UPDATE cs
|
||||
SET cs.realm_id = us.realm_id
|
||||
FROM %s cs
|
||||
INNER JOIN %s us ON cs.user_session_id = us.user_session_id AND cs.offline_flag = us.offline_flag AND cs.realm_id IS NULL"""
|
||||
.formatted(clientSessionTable, userSessionTable)));
|
||||
}
|
||||
|
||||
private void generateUpdateQueryForMySQL(String clientSessionTable, String userSessionTable) {
|
||||
statements.add(new RawParameterizedSqlStatement("""
|
||||
UPDATE %s cs
|
||||
INNER JOIN %s us ON cs.user_session_id = us.user_session_id AND cs.offline_flag = us.offline_flag AND cs.realm_id IS NULL
|
||||
SET cs.realm_id = us.realm_id"""
|
||||
.formatted(clientSessionTable, userSessionTable)));
|
||||
}
|
||||
|
||||
private void generateUpdateQueryForPostgresSQL(String clientSessionTable, String userSessionTable) {
|
||||
statements.add(new RawParameterizedSqlStatement("""
|
||||
UPDATE %s cs
|
||||
SET realm_id = us.realm_id
|
||||
FROM %s us
|
||||
WHERE cs.user_session_id = us.user_session_id AND cs.offline_flag = us.offline_flag AND cs.realm_id IS NULL"""
|
||||
.formatted(clientSessionTable, userSessionTable)));
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
|
||||
|
||||
entity.setTimestamp(clientSession.getTimestamp());
|
||||
entity.setData(model.getData());
|
||||
entity.setRealmId(adapter.getRealm().getId());
|
||||
|
||||
if (!exists) {
|
||||
em.persist(entity);
|
||||
@@ -272,14 +273,9 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
|
||||
|
||||
@Override
|
||||
public Map<String, Long> getUserSessionsCountsByClients(RealmModel realm, boolean offline) {
|
||||
|
||||
String offlineStr = offlineToString(offline);
|
||||
|
||||
TypedQuery<Object[]> query = em.createNamedQuery("findClientSessionsClientIds", Object[].class);
|
||||
|
||||
query.setParameter("offline", offlineStr);
|
||||
query.setParameter("realmId", realm.getId());
|
||||
query.setParameter("lastSessionRefresh", calculateOldestSessionTime(realm, offline));
|
||||
TypedQuery<Object[]> query = em.createNamedQuery("findClientSessionsClientIds", Object[].class)
|
||||
.setParameter("offline", offlineToString(offline))
|
||||
.setParameter("realmId", realm.getId());
|
||||
|
||||
return closing(query.getResultStream())
|
||||
.collect(Collectors.toMap(row -> {
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.hibernate.annotations.DynamicUpdate;
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="deleteClientSessionsByRealm", query="delete from PersistentClientSessionEntity sess where sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.realmId = :realmId)"),
|
||||
@NamedQuery(name="deleteClientSessionsByRealm", query="delete from PersistentClientSessionEntity sess where sess.realmId = :realmId"),
|
||||
@NamedQuery(name="deleteClientSessionsByClient", query="delete from PersistentClientSessionEntity sess where sess.clientId = :clientId and sess.clientId != 'external'"),
|
||||
@NamedQuery(name="deleteClientSessionsByExternalClient", query="delete from PersistentClientSessionEntity sess where sess.clientStorageProvider = :clientStorageProvider and sess.externalClientId = :externalClientId and sess.clientStorageProvider != 'internal'"),
|
||||
@NamedQuery(name="deleteClientSessionsByClientStorageProvider", query="delete from PersistentClientSessionEntity sess where sess.clientStorageProvider = :clientStorageProvider"),
|
||||
@@ -44,14 +44,18 @@ import org.hibernate.annotations.DynamicUpdate;
|
||||
@NamedQuery(name="deleteClientSessionsByUserSessions", query="delete from PersistentClientSessionEntity sess where sess.userSessionId in (:userSessionId) and sess.offline = :offline"),
|
||||
// The query "deleteExpiredClientSessions" is deprecated (since 26.5) and may be removed in the future.
|
||||
@NamedQuery(name="deleteExpiredClientSessions", query="delete from PersistentClientSessionEntity sess where sess.offline = :offline AND sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.realmId = :realmId AND u.offline = :offline AND u.lastSessionRefresh < :lastSessionRefresh)"),
|
||||
@NamedQuery(name="deleteClientSessionsByRealmSessionType", query="delete from PersistentClientSessionEntity sess where sess.offline = :offline AND sess.userSessionId IN (select u.userSessionId from PersistentUserSessionEntity u where u.realmId = :realmId and u.offline = :offline)"),
|
||||
@NamedQuery(name="deleteClientSessionsByRealmSessionType", query="delete from PersistentClientSessionEntity sess where sess.offline = :offline AND sess.realmId = :realmId"),
|
||||
@NamedQuery(name="findClientSessionsByUserSession", query="select sess from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and sess.offline = :offline"),
|
||||
@NamedQuery(name="findClientSessionsOrderedByIdInterval", query="select sess from PersistentClientSessionEntity sess where sess.offline = :offline and sess.userSessionId >= :fromSessionId and sess.userSessionId <= :toSessionId order by sess.userSessionId"),
|
||||
@NamedQuery(name="findClientSessionsOrderedByIdExact", query="select sess from PersistentClientSessionEntity sess where sess.offline = :offline and sess.userSessionId IN (:userSessionIds)"),
|
||||
@NamedQuery(name="findClientSessionsCountByClient", query="select count(sess) from PersistentClientSessionEntity sess where sess.offline = :offline and sess.clientId = :clientId and sess.clientId != 'external'"),
|
||||
@NamedQuery(name="findClientSessionsCountByExternalClient", query="select count(sess) from PersistentClientSessionEntity sess where sess.offline = :offline and sess.clientStorageProvider = :clientStorageProvider and sess.externalClientId = :externalClientId and sess.clientStorageProvider != 'internal'"),
|
||||
@NamedQuery(name="findClientSessionsByUserSessionAndClient", query="select sess from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and sess.offline = :offline and sess.clientId=:clientId and sess.clientId != 'external'"),
|
||||
@NamedQuery(name="findClientSessionsByUserSessionAndExternalClient", query="select sess from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and sess.offline = :offline and sess.clientStorageProvider = :clientStorageProvider and sess.externalClientId = :externalClientId and sess.clientStorageProvider != 'internal'")
|
||||
@NamedQuery(name="findClientSessionsByUserSessionAndExternalClient", query="select sess from PersistentClientSessionEntity sess where sess.userSessionId=:userSessionId and sess.offline = :offline and sess.clientStorageProvider = :clientStorageProvider and sess.externalClientId = :externalClientId and sess.clientStorageProvider != 'internal'"),
|
||||
@NamedQuery(name="findClientSessionsClientIds", query="SELECT sess.clientId, sess.externalClientId, sess.clientStorageProvider, count(sess)" +
|
||||
" FROM PersistentClientSessionEntity sess" +
|
||||
" WHERE sess.offline = :offline AND sess.realmId = :realmId" +
|
||||
" GROUP BY sess.clientId, sess.externalClientId, sess.clientStorageProvider"),
|
||||
})
|
||||
@Table(name="OFFLINE_CLIENT_SESSION")
|
||||
@Entity
|
||||
@@ -91,6 +95,9 @@ public class PersistentClientSessionEntity {
|
||||
@Column(name="DATA")
|
||||
protected String data;
|
||||
|
||||
@Column(name = "REALM_ID", length = 36)
|
||||
protected String realmId;
|
||||
|
||||
public String getUserSessionId() {
|
||||
return userSessionId;
|
||||
}
|
||||
@@ -147,6 +154,14 @@ public class PersistentClientSessionEntity {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
public static class Key implements Serializable {
|
||||
|
||||
protected String userSessionId;
|
||||
|
||||
@@ -63,10 +63,6 @@ import org.hibernate.annotations.DynamicUpdate;
|
||||
@NamedQuery(name="findUserSessionsByExternalClientId", query="SELECT sess FROM PersistentUserSessionEntity sess INNER JOIN PersistentClientSessionEntity clientSess " +
|
||||
" ON sess.userSessionId = clientSess.userSessionId AND clientSess.clientStorageProvider = :clientStorageProvider AND sess.offline = clientSess.offline AND clientSess.externalClientId = :externalClientId WHERE sess.offline = :offline " +
|
||||
" AND sess.realmId = :realmId AND sess.lastSessionRefresh >= :lastSessionRefresh ORDER BY sess.userSessionId"),
|
||||
@NamedQuery(name="findClientSessionsClientIds", query="SELECT clientSess.clientId, clientSess.externalClientId, clientSess.clientStorageProvider, count(clientSess)" +
|
||||
" FROM PersistentClientSessionEntity clientSess INNER JOIN PersistentUserSessionEntity sess ON clientSess.userSessionId = sess.userSessionId AND sess.offline = clientSess.offline" +
|
||||
" WHERE sess.offline = :offline AND sess.realmId = :realmId AND sess.lastSessionRefresh >= :lastSessionRefresh" +
|
||||
" GROUP BY clientSess.clientId, clientSess.externalClientId, clientSess.clientStorageProvider"),
|
||||
@NamedQuery(name = "findUserSessionAndDataWithNullRememberMeLastRefresh",
|
||||
query = "SELECT sess.userSessionId, sess.userId, sess.data" +
|
||||
" FROM PersistentUserSessionEntity sess" +
|
||||
|
||||
@@ -55,4 +55,46 @@
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="keycloak" id="26.6.0-44424-index-css-user-session-and-offline">
|
||||
<preConditions onFail="MARK_RAN" onSqlOutput="TEST">
|
||||
<not>
|
||||
<indexExists tableName="OFFLINE_CLIENT_SESSION" indexName="IDX_OFFLINE_CSS_BY_USER_SESSION_AND_OFFLINE"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<createIndex tableName="OFFLINE_CLIENT_SESSION" indexName="IDX_OFFLINE_CSS_BY_USER_SESSION_AND_OFFLINE">
|
||||
<column name="OFFLINE_FLAG" type="VARCHAR(4)"/>
|
||||
<column name="USER_SESSION_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="keycloak" id="26.6.0-44424-create-realm-in-client-session">
|
||||
<preConditions onFail="MARK_RAN" onSqlOutput="TEST">
|
||||
<not>
|
||||
<columnExists tableName="OFFLINE_CLIENT_SESSION" columnName="REALM_ID"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<addColumn tableName="OFFLINE_CLIENT_SESSION">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="keycloak" id="26.6.0-44424-set-realm-in-client-session">
|
||||
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.JpaUpdate26_6_0_OfflineClientSessionRealm"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="keycloak" id="26.6.0-44424-idx-css-realm-and-clients">
|
||||
<preConditions onFail="MARK_RAN" onSqlOutput="TEST">
|
||||
<not>
|
||||
<indexExists tableName="OFFLINE_CLIENT_SESSION" indexName="IDX_OFFLINE_CSS_BY_CLIENT_AND_REALM"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<createIndex tableName="OFFLINE_CLIENT_SESSION" indexName="IDX_OFFLINE_CSS_BY_CLIENT_AND_REALM">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
<column name="OFFLINE_FLAG" type="VARCHAR(4)"/>
|
||||
<column name="CLIENT_ID" type="VARCHAR(255)"/>
|
||||
<column name="CLIENT_STORAGE_PROVIDER" type="VARCHAR(36)"/>
|
||||
<column name="EXTERNAL_CLIENT_ID" type="VARCHAR(255)"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
||||
Reference in New Issue
Block a user