Keycloak:专用Infinispan群集的远程存储配置

Mic*_*ker 5 infinispan keycloak

最近,我加强了Keycloak部署,以使用专用的Infinispan群集作为remote-storeKeycloak各种缓存的额外持久层。更改本身进行得相当不错,尽管进行了更改之后,由于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
Run Code Online (Sandbox Code Playgroud)

通常,在同一时间段内从同一IP地址重复多次多次出现此错误消息。造成这种情况的原因似乎是最终用户的浏览器在登录时会无限重定向,直到浏览器本身停止循环为止。

我已经看到了各种GitHub问题(https://github.com/helm/charts/issues/8355),也记录了此行为,并且共识似乎是这是由Keycloak群集无法正确发现其成员引起的通过JGroups。

当您考虑到某些Keycloak缓存在中的默认配置中分布在Keycloak节点上时,此解释才有意义standalone-ha.xml。但是,我已将这些高速缓存修改为本地高速缓存,并remote-store指向我的新Infinispan群集,并且我认为我对该运行方式做出了一些不正确的假设,导致此错误开始发生。

这是我的Keycloak缓存配置的方式:

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
Run Code Online (Sandbox Code Playgroud)

请注意,与默认standalone-ha.xml配置文件相比,此缓存配置的大部分保持不变。我在这里所做的更改是将以下缓存更改为local并将它们指向我的远程Infinispan群集:

  • sessions
  • authenticationSessions
  • offlineSessions
  • clientSessions
  • offlineClientSessions
  • loginFailures
  • actionTokens
  • work

这是我的remote-cache服务器的配置:

<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>
Run Code Online (Sandbox Code Playgroud)

这是在Infinispan端配置我的缓存的方式:

<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>
Run Code Online (Sandbox Code Playgroud)

我相信我对带有远程存储的本地缓存的工作方式做出了一些错误的假设,我希望有人能够为我解决这个问题。我的意图是使Infinispan群集成为Keycloak所有缓存的真实来源。通过将每个缓存都设置为本地,我假设数据将通过Infinispan集群复制到每个Keycloak节点,这样,对本地authenticationSessions缓存的写入keycloak-0keycloak-1通过Infinispan集群同步持久化。

我认为正在发生的事情是,就将那个值持久保存到远程Infinispan群集而言,对Keycloak上的本地缓存的写入并不同步。换句话说,对authenticationSessions高速缓存执行写操作时,在等待将此值写入Infinispan群集时不会阻塞,因此在另一个Keycloak节点上立即读取此数据会导致本地和本地发生高速缓存未命中。 Infinispan集群。

我正在寻找一些帮助来确定为什么当前配置导致了此问题,并澄清了remote-store- 的行为-是否有办法将缓存写入由a支持的本地缓存remote-store以使其同步?如果没有,是否有更好的方法来完成我要在这里完成的工作?

其他一些潜在的相关细节:

  • Keycloak和Infinispan都被部署到Kubernetes集群中的相同名称空间。
  • 我正在KUBE_PING用于JGroups发现。
  • 使用Infinispan控制台,我可以验证复制到所有Infinispan节点的所有缓存中是否包含一定数量的条目-它们并未完全使用。
  • 如果将新领域添加到一个Keycloak节点,它将成功显示在其他Keycloak节点上,这使我相信work缓存将在所有Keycloak节点之间传播。
  • 如果我登录到一个Keycloak节点,则我的会话仍保留在其他Keycloak节点上,这使我相信与会话相关的缓存将在所有Keycloak节点上传播。
  • 我使用Keycloak的粘性会话作为对此的临时解决方案,但是我认为解决这些潜在的缓存问题是一种更永久的解决方案。

提前致谢!

Ari*_*era 15

当您在集群中配置 Keycloak 时,我将尝试澄清一些需要牢记的要点。

说到“无限重定向”这个话题,我多年前在开发环境中也遇到过类似的问题。虽然 keycloak 团队已经纠正了几个与无限循环相关的错误(例如KEYCLOAK-5856KEYCLOAK-5022KEYCLOAK-4717KEYCLOAK-4552KEYCLOAK-3878),但有时它会由于配置问题而发生。

检查站点是否为 HTTPS 的一件事是也通过 HTTPS 访问 Keycloak 服务器。

我记得当 Keycloak 被放置在 HTTPS 反向代理后面并且所需的标头没有传播到 Keycloak(标头 X-FOWARDED ...)时,我遇到了与无限循环类似的问题。环境搭建好了就解决了。当集群中的节点发现无法正常工作(JGroups)时,可能会发生类似的问题。

关于错误消息"expired_code",我将验证每个节点的时钟是否同步,因为它可能导致这种过期令牌/代码错误。

现在更好地了解您的配置,将“本地缓存”模式与远程存储一起使用似乎并不合适,指向 infinispan 集群。

尽管通常共享存储(例如远程缓存)通常与失效缓存一起使用,避免集群复制完整数据(请参阅可以在此处应用的评论https://developer.jboss .org/message/986847#986847),分布式缓存或失效缓存可能没有太大区别。

我相信带有远程存储的分布式缓存会更好(或失效缓存以避免将大量数据复制给所有者)但是我无法确保“本地缓存”如何与远程存储(共享)一起使用因为我从未尝试过这种配置。我会首先选择测试分布式缓存或失效缓存,这是由它如何处理被驱逐/失效的数据给出的。通常,本地缓存不会与集群中的其他远程节点同步。如果这种实现在内存中保留本地映射,很可能即使远程存储中的数据被修改,这些更改在某些情况下也可能无法反映。我可以给你一个 Jmeter 测试文件,你可以使用它,这样你就可以尝试使用这两种配置执行自己的测试。

回到你的配置主题,除了复制缓存有一定的局限性之外,你还必须考虑到复制缓存通常比只将数据复制到定义的所有者的分布式缓存慢一点(复制的缓存全部写入节点)。还有一个叫做scatter-cache的变体,它表现更好,但例如缺乏事务支持(你可以在这里看到一个比较图表https://infinispan.org/docs/stable/user_guide/user_guide.html#which_cache_mode_should_i_use)。由于需要发送的复制消息的数量,复制通常只在小集群(低于 8 或 10 个服务器)中表现良好。分布式缓存允许 Infinispan 通过按条目定义多个副本来线性扩展。

进行您尝试执行的类型的配置而不是类似于 Keycloak (standalone-ha.xml) 建议的配置的主要原因是当您需要独立扩展应用程序的 infinispan 集群或使用infinispan 作为持久存储。

我将解释 Keycloak 如何管理它的缓存以及它如何基本上将它分成两三组,以便您更好地了解您需要的配置。

通常,要在集群中配置 Keycloak,只需在 HA 模式下提升和配置 Keycloak,就像使用 Wildfly 的传统实例一样。如果观察keycloak安装中的standalone.xml和standalone-ha.xml的区别,会发现基本支持添加到“Jgroups”、“modcluster”,缓存是分布式的(以前是本地的) ) 在 Wildfly / Keycloak (HA) 中的节点之间。

详细

  • 添加jgroups子系统,负责连接集群节点,进行集群内的消息/通信。JGroups 提供网络通信能力、可靠通信和其他特性,如节点发现、点对点通信、多播通信、故障检测和集群节点之间的数据传输。
  • EJB3 缓存从一个 SIMPLE 缓存(在没有事务处理的本地内存中)转到一个 DISTRIBUTED。但是,根据我扩展该项目的经验,我会确保 Keycloak 项目不需要使用 EJB3。
  • 缓存:“领域”、“用户”、“授权”和“密钥”都保存在本地,因为它们仅用于减少数据库的负载。
  • cache: "work" 变为 REPLICATED 因为它是 Keycloak 用来通知集群节点缓存条目必须被驱逐/无效的条目,因为它的状态已被修改。
  • 缓存“sessions”、“authenticationSessions”、“offlineSessions”、“clientSessions”、“offlineSessions”、“loginFailures”和“actionTokens”变为 DISTRIBUTED,因为它们的性能优于复制缓存(请参阅https://infinispan.org/docs /stable/user_guide/user_guide.html#which_cache_mode_should_i_use),因为您只需要将数据复制给所有者。
  • keycloak 为其默认 HA 配置提出的其他更改是分发“web”和“ejb”(及更高版本)缓存容器,并将“hibernate”缓存更改为“invalidation-cache”(类似于本地缓存,但带有失效)同步)。

我认为你的缓存配置应该被定义为“分布式缓存”,比如“sessions”、“authenticationSessions”、“offlineSessions”、“clientSessions”、“offlineClientSessions”、“loginFailures”和“actionTokens”(而不是“local ”)。但是,因为您使用的是远程共享存储,所以您应该像我之前所说的那样对其进行测试,看看它是如何工作的。

此外,名为“work”的缓存应为“replicated-cache”,其他缓存(“keys”、“authorization”、“realms”和“users”)应定义为“local-cache”。

在您的 infinispan 集群中,您可以将其定义为“分布式缓存”(或“复制缓存”)。

请记住:

在复制缓存中,集群中的所有节点都持有所有键,即如果一个键存在于一个节点上,它也将存在于所有其他节点上。在分布式缓存中,维护多个副本以提供冗余和容错,但这通常远少于集群中的节点数量。分布式缓存提供比复制缓存更大程度的可伸缩性。分布式缓存还能够在集群中透明地定位键,并提供 L1 缓存,用于对远程存储的状态进行快速本地读取访问。您可以在相关的用户指南章节中阅读更多内容。

Infinispan 文档。参考:缓存模式

正如 Keycloak (6.0) 文档所说:

Keycloak 有两种类型的缓存。一种类型的缓存位于数据库的前面,通过将数据保存在内存中来减少数据库的负载并减少总体响应时间。领域、客户端、角色和用户元数据保存在这种类型的缓存中。此缓存是本地缓存。即使您在具有更多 Keycloak 服务器的集群中,本地缓存也不使用复制。相反,它们只在本地保留副本,如果条目被更新,则会向集群的其余部分发送一条无效消息,并且该条目将被逐出。有单独的复制缓存工作,该任务是向整个集群发送关于哪些条目应该从本地缓存中驱逐的失效消息。这大大减少了网络流量,提高了效率,并避免了通过网络传输敏感元数据。

第二种缓存处理管理用户会话、离线令牌和跟踪登录失败,以便服务器可以检测密码网络钓鱼和其他攻击。这些缓存中保存的数据是临时的,仅在内存中,但可能会跨集群复制。

博士。参考:缓存配置

如果你想阅读另一个好的文档,你可以看看“cross-dc”部分(cross-dc mode)特别是“3.4.6 Infinispan cache”(infinispan cache)部分

我尝试了 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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

PS 将属性“所有者”从 1 更改为您喜欢的值。

我希望能有所帮助。