最近,我加强了我的Keycloak部署,使用专用的Infinispan集群作为remote-store
,为Keycloak的各种缓存提供额外的持久性层。更改本身进行得相当顺利,但在进行此更改后,我们开始看到很多登录错误,由于expired_code
错误消息:
WARN [org.keycloak.events] (default task-2007) type=LOGIN_ERROR, realmId=my-realm, clientId=null, userId=null, ipAddress=192.168.50.38, error=expired_code, restart_after_timeout=true
这个错误信息通常在短时间内以相同的IP地址重复出现数十次。原因似乎是最终用户的浏览器在登录时无限重定向,直到浏览器本身停止循环。
我看到了一些GitHub问题(https://github.com/helm/charts/issues/8355)也记录了这种行为,共识似乎是这是由于Keycloak集群无法通过JGroups正确发现其成员所致。
当考虑到默认配置中一些Keycloak缓存在Keycloak节点之间分布时,这种解释是有道理的。但是,我已经修改了这些缓存以成为具有指向我的新Infinispan集群的
remote-store
的本地缓存,并且我认为我对此的工作方式做出了一些不正确的假设,导致此错误开始发生。以下是我的Keycloak缓存配置方式:
<subsystem xmlns="urn:jboss:domain:infinispan:7.0">
<cache-container name="keycloak" module="org.keycloak.keycloak-model-infinispan">
<transport lock-timeout="60000"/>
<local-cache name="realms">
<object-memory size="10000"/>
</local-cache>
<local-cache name="users">
<object-memory size="10000"/>
</local-cache>
<local-cache name="authorization">
<object-memory size="10000"/>
</local-cache>
<local-cache name="keys">
<object-memory size="1000"/>
<expiration max-idle="3600000"/>
</local-cache>
<local-cache name="sessions">
<remote-store cache="sessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
<property name="rawValues">
true
</property>
<property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
</property>
</remote-store>
</local-cache>
<local-cache name="authenticationSessions">
<remote-store cache="authenticationSessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
<property name="rawValues">
true
</property>
<property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
</property>
</remote-store>
</local-cache>
<local-cache name="offlineSessions">
<remote-store cache="offlineSessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
<property name="rawValues">
true
</property>
<property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
</property>
</remote-store>
</local-cache>
<local-cache name="clientSessions">
<remote-store cache="clientSessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
<property name="rawValues">
true
</property>
<property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
</property>
</remote-store>
</local-cache>
<local-cache name="offlineClientSessions">
<remote-store cache="offlineClientSessions" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
<property name="rawValues">
true
</property>
<property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
</property>
</remote-store>
</local-cache>
<local-cache name="loginFailures">
<remote-store cache="loginFailures" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
<property name="rawValues">
true
</property>
<property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
</property>
</remote-store>
</local-cache>
<local-cache name="actionTokens">
<remote-store cache="actionTokens" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
<property name="rawValues">
true
</property>
<property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
</property>
</remote-store>
</local-cache>
<replicated-cache name="work">
<remote-store cache="work" remote-servers="remote-cache" fetch-state="false" passivation="false" preload="false" purge="false" shared="true">
<property name="rawValues">
true
</property>
<property name="marshaller">
org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory
</property>
</remote-store>
</replicated-cache>
</cache-container>
<cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
<transport lock-timeout="60000"/>
<replicated-cache name="default">
<transaction mode="BATCH"/>
</replicated-cache>
</cache-container>
<cache-container name="web" default-cache="dist" module="org.wildfly.clustering.web.infinispan">
<transport lock-timeout="60000"/>
<distributed-cache name="dist">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store/>
</distributed-cache>
</cache-container>
<cache-container name="ejb" aliases="sfsb" default-cache="dist" module="org.wildfly.clustering.ejb.infinispan">
<transport lock-timeout="60000"/>
<distributed-cache name="dist">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store/>
</distributed-cache>
</cache-container>
<cache-container name="hibernate" module="org.infinispan.hibernate-cache">
<transport lock-timeout="60000"/>
<local-cache name="local-query">
<object-memory size="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<invalidation-cache name="entity">
<transaction mode="NON_XA"/>
<object-memory size="10000"/>
<expiration max-idle="100000"/>
</invalidation-cache>
<replicated-cache name="timestamps"/>
</cache-container>
</subsystem>
请注意,与默认的
standalone-ha.xml
配置文件相比,大部分缓存配置都没有改变。我在这里所做的更改是将以下缓存更改为local
并将它们指向我的远程Infinispan集群:
sessions
authenticationSessions
offlineSessions
clientSessions
offlineClientSessions
loginFailures
actionTokens
work
remote-cache
服务器的配置:<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
<!-- Default socket bindings from standalone-ha.xml are not listed here for brevity -->
<outbound-socket-binding name="remote-cache">
<remote-destination host="${env.INFINISPAN_HOST}" port="${remote.cache.port:11222}"/>
</outbound-socket-binding>
</socket-binding-group>
以下是我在Infinispan方面的缓存配置:
<subsystem xmlns="urn:infinispan:server:core:9.4" default-cache-container="clustered">
<cache-container name="clustered" default-cache="default">
<transport lock-timeout="60000"/>
<global-state/>
<replicated-cache-configuration name="replicated-keycloak" mode="SYNC">
<locking acquire-timeout="3000" />
</replicated-cache-configuration>
<replicated-cache name="work" configuration="replicated-keycloak"/>
<replicated-cache name="sessions" configuration="replicated-keycloak"/>
<replicated-cache name="authenticationSessions" configuration="replicated-keycloak"/>
<replicated-cache name="clientSessions" configuration="replicated-keycloak"/>
<replicated-cache name="offlineSessions" configuration="replicated-keycloak"/>
<replicated-cache name="offlineClientSessions" configuration="replicated-keycloak"/>
<replicated-cache name="actionTokens" configuration="replicated-keycloak"/>
<replicated-cache name="loginFailures" configuration="replicated-keycloak"/>
</cache-container>
</subsystem>
我认为我对本地缓存与远程存储如何工作有一些错误的假设,希望有人能为我澄清。我的目的是使Infinispan集群成为Keycloak所有缓存的真正数据源。通过将每个缓存设置为本地,我认为数据会通过Infinispan集群复制到每个Keycloak节点,这样在keycloak-0上对本地authenticationSessions缓存的写入将同步持久化到通过Infinispan集群连接的keycloak-1上。
我认为正在发生的是,在Keycloak上对本地缓存的写操作与将该值持久化到远程Infinispan集群不同步。换句话说,当对authenticationSessions缓存执行写入操作时,它并不会阻塞等待该值被写入Infinispan集群,因此在另一个Keycloak节点上立即读取此数据会导致本地和Infinispan集群中的缓存未命中。
我正在寻求一些帮助,以确定当前配置为什么会导致此问题,并且需要对remote-store的行为进行澄清-是否有一种方法可以使对由remote-store支持的本地缓存的写入是同步的?如果没有,有没有更好的方法来实现我在这里尝试完成的任务?
以下是其他一些可能相关的细节:
- Keycloak和Infinispan都部署在Kubernetes集群的同一命名空间中。
- 我正在使用
KUBE_PING
进行JGroups发现。 - 使用Infinispan控制台,我能够验证所有缓存都复制到了所有Infinispan节点,并且它们都有一定数量的条目 - 它们并没有完全未使用。
- 如果我在一个Keycloak节点上添加一个新的realm,它会成功显示在其他Keycloak节点上,这让我相信
work
缓存正在在所有Keycloak节点之间传播。 - 如果我登录到一个Keycloak节点,我的会话会保留在其他Keycloak节点上,这让我相信与会话相关的缓存正在在所有Keycloak节点之间传播。
- 我正在为Keycloak使用粘性会话作为临时解决方案,但我相信修复这些潜在的缓存问题是更持久的解决方案。
提前致谢!