最近,我加强了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缓存在standalone-ha.xml
内的默认配置中分布在Keycloak节点上时,此解释才有意义。但是,我已将这些缓存修改为本地缓存,其中remote-store
指向我的新Infinispan群集,并且我认为我对此工作原理有一些不正确的假设,导致此错误开始发生。
这是配置我的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节点,这样对authenticationSessions
上的本地keycloak-0
缓存的写操作将被同步保存到{{1} }通过Infinispan集群。
我认为正在发生的事情是,就将那个值持久保存到远程Infinispan集群而言,对Keycloak上的本地缓存的写入并不同步。换句话说,对keycloak-1
缓存执行写入操作时,在等待将此值写入Infinispan群集时不会阻塞,因此立即在另一个Keycloak节点上读取此数据将导致缓存本地和Infinispan集群中的小姐。
我正在寻找一些帮助,以找出为什么当前配置导致此问题的原因,以及对authenticationSessions
的行为的一些说明-是否有办法将高速缓存写入由a支持的本地高速缓存remote-store
要同步吗?如果没有,是否有更好的方法来完成我要在这里完成的工作?
一些其他可能相关的详细信息:
remote-store
进行JGroups发现。KUBE_PING
缓存正在所有Keycloak节点上传播。谢谢!
答案 0 :(得分:5)
在集群中配置Keycloak时,我会尝试澄清一些要点。
谈论“无限重定向” 的主题,几年前,我在开发环境中遇到了类似的问题。尽管密钥斗篷团队已纠正了与无限循环相关的多个错误(例如KEYCLOAK-5856,KEYCLOAK-5022,KEYCLOAK-4717,KEYCLOAK-4552,KEYCLOAK-3878),但有时由于配置问题。
检查站点是否为HTTPS的一件事就是也要通过HTTPS访问Keycloak服务器。
我记得当Keycloak放置在HTTPS反向代理后面并且所需的标头没有传播到Keycloak时,遇到了与无限循环类似的问题(标头X-FOWARDED ...)。解决了良好设置环境的问题。当集群中的节点发现无法正常工作(JGroups)时,可能会发生类似的问题。
关于错误消息“ expired_code” ,我将验证每个节点的时钟是否同步,因为它可能导致这种过期的令牌/代码错误。
现在更好地理解了您的配置,将“本地缓存”模式与远程存储一起使用并指向infinispan集群似乎并不不合适。
尽管通常,共享存储区(例如远程缓存)通常与失效缓存一起使用,在这种情况下,集群避免复制完整的数据(请参见在此处可以应用的评论{{3} }),分布式缓存或失效缓存可能不会有太大的区别。
我相信带有远程存储的分布式缓存会更好(或使用无效缓存以避免将繁重的数据复制给所有者),但是我无法确保“本地缓存”如何与远程存储一起使用(共享),因为我从未尝试过这种配置。 我将首先选择测试分布式缓存或失效缓存,该缓存由其如何处理逐出/失效的数据给出。通常,本地缓存不与群集中的其他远程节点同步。如果这种实现将本地映射保存在内存中,则即使修改了远程存储中的数据,在某些情况下也可能不会反映这些更改。 我可以给您一个Jmeter测试文件,您可以使用它,以便尝试使用两种配置执行自己的测试。
返回到配置主题,除了复制的缓存具有某些限制之外,还必须考虑到这些因素,并且通常比只将数据复制到定义的所有者的分布式缓存(复制的缓存)要慢一些。写入所有节点)。还有一个称为分散式缓存的变体,其性能更好,但是例如缺少事务支持(您可以在此处看到比较图https://developer.jboss.org/message/986847#986847)。 由于需要发送的复制消息数量众多,因此复制通常仅在小型群集(8台或10台以下服务器)中表现良好。分布式缓存允许Infinispan通过按条目定义多个副本来线性扩展。
进行配置的主要原因是,当您需要独立扩展该实例的infinispan集群时,而不是与Keycloak(standalone-ha.xml)提出的配置类似。应用程序或将infinispan用作持久存储。
我将解释Keycloak如何管理其缓存以及基本上如何将其分为两组或三组,以便您可以更好地了解所需的配置。
通常,要在集群中配置Keycloak,只需像在传统的Wildfly实例中那样在HA模式下提升并配置Keycloak。如果有人观察到keycloak安装中随附的standalone.xml和standalone-ha.xml之间的差异,则您会注意到,基本上已将支持添加到“ Jgroups”,“ modcluster”中,并且缓存已分发(以前是本地的) )在Wildfly / Keycloak(HA)中的节点之间。
详细信息:
我认为对于诸如“会话”,“ authenticationSessions”,“ offlineSessions”,“ clientSessions”,“ offlineClientSessions”,“ loginFailures”和“ actionTokens”之类的缓存,应将您的缓存配置定义为“分布式缓存”的“本地”)。但是,由于您使用的是远程共享存储,因此应该对其进行测试,以查看其工作方式,如我之前所说。
此外,名为“ work”的缓存应为“ replicated-cache”,而其他缓存(“键”,“授权”,“领域”和“用户”)应定义为“ local-cache”。
在infinispan群集中,您可以将其定义为“分布式缓存”(或“复制缓存”)。
请记住:
在复制的缓存中,集群中的所有节点都拥有所有密钥,即 键存在于一个节点上,也将存在于所有其他节点上。在一个 分布式缓存,维护许多副本以提供 冗余和容错能力,但是通常要少得多 比群集中的节点数多。分布式缓存提供 比复制的缓存具有更大的可扩展性。 分布式缓存还可以透明地跨 集群,并提供L1高速缓存以快速状态的本地读取访问 远程存储。您可以在相关的《用户指南》中阅读更多内容 章。
Infinispan文档。参考:https://infinispan.org/docs/stable/user_guide/user_guide.html#which_cache_mode_should_i_use
如Keycloak(6.0)文档所述:
Keycloak具有两种类型的缓存。一种类型的缓存位于 数据库以减少DB上的负载并减少总体 通过将数据保存在内存中来缩短响应时间。领域,客户,角色和 用户元数据保存在这种类型的缓存中。该缓存是本地的 缓存。即使您位于本地缓存中,本地缓存也不使用复制 具有更多Keycloak服务器的群集。相反,他们只保留副本 本地,并且如果条目已更新,则会发送无效消息至 集群的其余部分和条目被逐出。有分开的 复制缓存工作,该任务是发送无效消息 有关整个集群的信息,应从本地逐出 缓存。这样可以大大减少网络流量,使事情变得高效, 并避免通过网络传输敏感的元数据。
第二种缓存类型可离线管理用户会话 令牌,并跟踪登录失败,以便服务器可以 检测密码网络钓鱼和其他攻击。这些中保存的数据 缓存是临时的,仅在内存中,但可能会在 集群。
文档参考:cache mode
如果您想阅读另一个好的文档,可以查看“跨DC”部分(cache configuration),尤其是“ 3.4.6 Infinispan缓存”部分(cross-dc mode)
我尝试使用Keycloak 6.0.1和Infinispan 9.4.11.Final,这是我的测试配置(基于standalone-ha.xml文件)。
Keycloak infinispan子系统:
<subsystem xmlns="urn:jboss:domain:infinispan:8.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>
<distributed-cache name="sessions" owners="1" remote-timeout="30000">
<remote-store cache="sessions" remote-servers="remote-cache" socket-timeout="60000" 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>
</distributed-cache>
<distributed-cache name="authenticationSessions" owners="1" remote-timeout="30000">
<remote-store cache="authenticationSessions" remote-servers="remote-cache" socket-timeout="60000" 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>
</distributed-cache>
<distributed-cache name="offlineSessions" owners="1" remote-timeout="30000">
<remote-store cache="offlineSessions" remote-servers="remote-cache" socket-timeout="60000" 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>
</distributed-cache>
<distributed-cache name="clientSessions" owners="1" remote-timeout="30000">
<remote-store cache="clientSessions" remote-servers="remote-cache" socket-timeout="60000" 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>
</distributed-cache>
<distributed-cache name="offlineClientSessions" owners="1" remote-timeout="30000">
<remote-store cache="offlineClientSessions" remote-servers="remote-cache" socket-timeout="60000" 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>
</distributed-cache>
<distributed-cache name="loginFailures" owners="1" remote-timeout="30000">
<remote-store cache="loginFailures" remote-servers="remote-cache" socket-timeout="60000" 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>
</distributed-cache>
<replicated-cache name="work"/>
<local-cache name="authorization">
<object-memory size="10000"/>
</local-cache>
<local-cache name="keys">
<object-memory size="1000"/>
<expiration max-idle="3600000"/>
</local-cache>
<distributed-cache name="actionTokens" owners="1" remote-timeout="30000">
<remote-store cache="actionTokens" remote-servers="remote-cache" socket-timeout="60000" 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>
<object-memory size="-1"/>
<expiration max-idle="-1" interval="300000"/>
</distributed-cache>
</cache-container>
Keycloak套接字绑定:
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
<socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
<socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
<socket-binding name="http" port="${jboss.http.port:8080}"/>
<socket-binding name="https" port="${jboss.https.port:8443}"/>
<socket-binding name="jgroups-mping" interface="private" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45700"/>
<socket-binding name="jgroups-tcp" interface="private" port="7600"/>
<socket-binding name="jgroups-udp" interface="private" port="55200" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45688"/>
<socket-binding name="modcluster" multicast-address="${jboss.modcluster.multicast.address:224.0.1.105}" multicast-port="23364"/>
<socket-binding name="txn-recovery-environment" port="4712"/>
<socket-binding name="txn-status-manager" port="4713"/>
<outbound-socket-binding name="remote-cache">
<remote-destination host="my-server-domain.com" port="11222"/>
</outbound-socket-binding>
<outbound-socket-binding name="mail-smtp">
<remote-destination host="localhost" port="25"/>
</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" statistics="true">
<transport lock-timeout="60000"/>
<global-state/>
<distributed-cache-configuration name="transactional">
<transaction mode="NON_XA" locking="PESSIMISTIC"/>
</distributed-cache-configuration>
<distributed-cache-configuration name="async" mode="ASYNC"/>
<replicated-cache-configuration name="replicated"/>
<distributed-cache-configuration name="persistent-file-store">
<persistence>
<file-store shared="false" fetch-state="true"/>
</persistence>
</distributed-cache-configuration>
<distributed-cache-configuration name="indexed">
<indexing index="LOCAL" auto-config="true"/>
</distributed-cache-configuration>
<distributed-cache-configuration name="memory-bounded">
<memory>
<binary size="10000000" eviction="MEMORY"/>
</memory>
</distributed-cache-configuration>
<distributed-cache-configuration name="persistent-file-store-passivation">
<memory>
<object size="10000"/>
</memory>
<persistence passivation="true">
<file-store shared="false" fetch-state="true">
<write-behind modification-queue-size="1024" thread-pool-size="1"/>
</file-store>
</persistence>
</distributed-cache-configuration>
<distributed-cache-configuration name="persistent-file-store-write-behind">
<persistence>
<file-store shared="false" fetch-state="true">
<write-behind modification-queue-size="1024" thread-pool-size="1"/>
</file-store>
</persistence>
</distributed-cache-configuration>
<distributed-cache-configuration name="persistent-rocksdb-store">
<persistence>
<rocksdb-store shared="false" fetch-state="true"/>
</persistence>
</distributed-cache-configuration>
<distributed-cache-configuration name="persistent-jdbc-string-keyed">
<persistence>
<string-keyed-jdbc-store datasource="java:jboss/datasources/ExampleDS" fetch-state="true" preload="false" purge="false" shared="false">
<string-keyed-table prefix="ISPN">
<id-column name="id" type="VARCHAR"/>
<data-column name="datum" type="BINARY"/>
<timestamp-column name="version" type="BIGINT"/>
</string-keyed-table>
<write-behind modification-queue-size="1024" thread-pool-size="1"/>
</string-keyed-jdbc-store>
</persistence>
</distributed-cache-configuration>
<distributed-cache name="default"/>
<replicated-cache name="repl" configuration="replicated"/>
<replicated-cache name="work" configuration="replicated"/>
<replicated-cache name="sessions" configuration="replicated"/>
<replicated-cache name="authenticationSessions" configuration="replicated"/>
<replicated-cache name="clientSessions" configuration="replicated"/>
<replicated-cache name="offlineSessions" configuration="replicated"/>
<replicated-cache name="offlineClientSessions" configuration="replicated"/>
<replicated-cache name="actionTokens" configuration="replicated"/>
<replicated-cache name="loginFailures" configuration="replicated"/>
</cache-container>
</subsystem>
P.S。将属性“所有者”从1更改为您喜欢的值。
希望能对您有所帮助。
答案 1 :(得分:0)
这里的交流很好,令人难以置信,我的假设与您与Michael完全相同,我将本地缓存配置为使用远程存储,并期望密钥将始终在远程存储中进行读写,但显然不是它的工作原理。
从这里完成的所有交换中,我很难过,这是为什么,为什么我们不能将本地infinispan配置为仅作为远程infinispan的代理,从而使该实例保持无状态,并变得更容易重新部署的过程。