Keycloak 分布式部署中会话过期清理机制
在 Keycloak 分布式部署(使用外部独立部署的 Infinispan)的架构下,sessions 和 clientSessions 的过期清理涉及两种不同的部署模式,机制略有不同:
架构模式 1:Embedded + Remote Store(嵌入式缓存 + 远程存储)
这种模式下,Keycloak 节点有本地嵌入式缓存,同时配置了远程存储(Remote Store)连接到外部 Infinispan 集群。- /**
- * @author Marek Posolda
- */
- @ClientListener
- public class RemoteCacheSessionListener<K, V extends SessionEntity> {
复制代码 清理机制:
- 本地缓存自动过期:
- 当会话数据写入本地嵌入式缓存(DefaultSegmentedDataContainer)时,会同时设置 lifespan 和 maxIdle 参数
- Infinispan 的内置过期机制会自动清除过期条目
- 远程缓存事件同步:
- RemoteCacheSessionListener 通过 Hot Rod Client Listener 机制监听远程缓存事件
- 当远程 Infinispan 中条目被删除时,会触发 @ClientCacheEntryRemoved 事件:
- @ClientCacheEntryRemoved
- public void removed(ClientCacheEntryRemovedEvent event) {
- K key = (K) event.getKey();
- if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
- this.executor.submit(event, () -> {
- // We received event from remoteCache, so we won't update it back
- cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES)
- .remove(key);
- });
- }
- }
复制代码
- 重要限制:
- Infinispan 不发送过期事件(Expiration Event)给 Hot Rod 客户端监听器!
- 远程 Infinispan 中的条目过期时,不会主动通知 Keycloak 节点
- 本地缓存的清理完全依赖于本地 Infinispan 的自动过期机制
架构模式 2:Remote Only(纯远程模式)
这种模式下,Keycloak 不维护本地会话缓存,所有会话数据都直接存储在外部 Infinispan 集群中。- @Override
- public void removeAllExpired() {
- //rely on Infinispan expiration
- }
- @Override
- public void removeExpired(RealmModel realm) {
- //rely on Infinispan expiration
- }
复制代码 清理机制:
- 完全依赖远程 Infinispan 的过期机制
- Keycloak 本地 JVM 中没有 DefaultSegmentedDataContainer,因为不使用嵌入式缓存
- 所有读取操作直接访问远程缓存,过期数据自然不会被读取到
关键代码:过期时间计算
无论哪种模式,会话的过期时间都通过 SessionTimeouts 计算:- public static long getUserSessionLifespanMs(RealmModel realm, ClientModel client, UserSessionEntity userSessionEntity) {
- return getUserSessionLifespanMs(realm, false, userSessionEntity.isRememberMe(), userSessionEntity.getStarted());
- }
- public static long getUserSessionLifespanMs(RealmModel realm, boolean offline, boolean rememberMe, int started) {
- long lifespan = SessionExpirationUtils.calculateUserSessionMaxLifespanTimestamp(offline, rememberMe,
- TimeUnit.SECONDS.toMillis(started), realm);
- if (offline && lifespan == IMMORTAL_FLAG) {
- return IMMORTAL_FLAG;
- }
- lifespan = lifespan - Time.currentTimeMillis();
- if (lifespan <= 0) {
- return ENTRY_EXPIRED_FLAG;
- }
- return lifespan;
- }
复制代码- @Override
- public void removeAllExpired() {
- //rely on Infinispan expiration
- }
- @Override
- public void removeExpired(RealmModel realm) {
- //rely on Infinispan expiration
- }
复制代码 [code] public static long getUserSessionLifespanMs(RealmModel realm, ClientModel client, UserSessionEntity userSessionEntity) { return getUserSessionLifespanMs(realm, false, userSessionEntity.isRememberMe(), userSessionEntity.getStarted()); } public static long getUserSessionLifespanMs(RealmModel realm, boolean offline, boolean rememberMe, int started) { long lifespan = SessionExpirationUtils.calculateUserSessionMaxLifespanTimestamp(offline, rememberMe, TimeUnit.SECONDS.toMillis(started), realm); if (offline && lifespan == IMMORTAL_FLAG) { return IMMORTAL_FLAG; } lifespan = lifespan - Time.currentTimeMillis(); if (lifespan |