Solución:
Intentaré aclarar algunos puntos a tener en cuenta a la hora de configurar Keycloak en clúster.
Hablando del tema de “redireccionamientos infinitos”, He experimentado un problema similar en entornos de desarrollo hace años. Si bien el equipo de keycloak ha corregido varios errores relacionados con bucles infinitos (por ejemplo, KEYCLOAK-5856, KEYCLOAK-5022, KEYCLOAK-4717, KEYCLOAK-4552, KEYCLOAK-3878) a veces sucede debido a problemas de configuración.
Una cosa para verificar si el sitio es HTTPS es acceder a un servidor Keycloak por HTTPS también.
Recuerdo que sufrí un problema similar al bucle infinito cuando el Keycloak se colocó detrás de un proxy inverso HTTPS y los encabezados necesarios no se propagaron al Keycloak (encabezados X-FOWARDED …). Se solucionó configurando bien el entorno. Puede suceder un problema similar cuando el descubrimiento de nodos en el clúster no funciona correctamente (JGroups).
Sobre el mensaje de error “expired_code”, Verificaría que los relojes de cada nodo estén sincronizados, ya que puede provocar este tipo de error de token / código caducado.
Ahora que comprende mejor su configuración, no parece inapropiado usar el modo “caché local” con un almacén remoto, apuntando al clúster infinispan.
Aunque, por lo general, el almacén compartido (como un caché remoto) generalmente se usa con un caché de invalidación donde se evita replicar los datos completos por el clúster (ver comentario que se puede aplicar aquí https: //developer.jboss .org / message / 986847 # 986847), puede que no haya grandes diferencias con una caché distribuida o de invalidación.
Creo que un caché distribuido con un almacén remoto se aplicaría mejor (o un caché de invalidación para evitar replicar datos pesados a los propietarios); sin embargo, no pude asegurar cómo funciona un “caché local” con un almacenamiento remoto (compartido) ya que nunca he probado este tipo de configuración. Primero elegiría probar una caché distribuida o una caché de invalidación dada por cómo funciona con los datos desalojados / invalidados. Normalmente, las cachés locales no se sincronizan con otros nodos remotos del clúster. Si este tipo de implementación mantiene un mapa local en la memoria, es probable que incluso si se modifican los datos en el almacenamiento remoto, estos cambios pueden no reflejarse en algunas situaciones. Puedo darle un archivo de prueba de Jmeter que puede usar para que pueda intentar realizar sus propias pruebas con ambas configuraciones.
Volviendo al tema de tu configuración, debes tener en cuenta además que las caché replicadas tienen ciertas limitaciones y suelen ser un poco más lentas que las distribuidas que solo replican los datos a los propietarios definidos (las replicadas escriben en todas los nodos). También hay una variante llamada caché dispersa que funciona mejor pero, por ejemplo, carece de compatibilidad con transacciones (puede ver aquí un cuadro comparativo https://infinispan.org/docs/stable/user_guide/user_guide.html#which_cache_mode_should_i_use). La replicación generalmente solo funciona bien en clústeres pequeños (menos de 8 o 10 servidores), debido a la cantidad de mensajes de replicación que deben enviarse. La caché distribuida permite a Infinispan escalar linealmente definiendo una cantidad de réplicas por entrada.
La razón principal para realizar una configuración del tipo que estás intentando hacer en lugar de una similar a la propuesta por Keycloak (standalone-ha.xml), es cuando tienes el requisito de escalar de forma independiente el clúster infinispan de la aplicación o usando infinispan como una tienda persistente.
Explicaré cómo Keycloak administra su caché y cómo lo divide en dos o tres grupos básicamente para que puedas comprender mejor la configuración que necesitas.
Por lo general, para configurar Keycloak en un clúster, simplemente active y configure Keycloak en modo HA tal como lo haría con una instancia tradicional de Wildfly. Si uno observa las diferencias entre el standalone.xml y el standalone-ha.xml que viene en la instalación de keycloak, uno nota que básicamente se agrega soporte a “Jgroups”, “modcluster”, y las cachés se distribuyen (que antes eran locales ) entre los nodos de Wildfly / Keycloak (HA).
En detalle:
- Se agrega el subsistema jgroups, que se encargará de conectar los nodos del clúster y realizar la mensajería / comunicación en el clúster. JGroups proporciona capacidades de comunicación de red, comunicaciones confiables y otras características como descubrimiento de nodos, comunicaciones de punto a punto, comunicación de multidifusión, detección de fallas y transferencia de datos entre nodos de clúster.
- la caché EJB3 pasa de una caché SIMPLE (en la memoria local sin manejo de transacciones) a una DISTRIBUIDA. Sin embargo, me aseguraría de que el proyecto Keycloak no requiera el uso de EJB3 de acuerdo con mi experiencia al extender este proyecto.
- caché: “reinos”, “usuarios”, “autorización” y “claves” se mantienen locales ya que solo se utilizan para reducir la carga en la base de datos.
- caché: “trabajo” se vuelve REPLICADO ya que es el que Keycloak usa para notificar a los nodos del clúster que una entrada de la caché debe ser desalojada / invalidada ya que su estado ha sido modificado.
- caché “sesiones”, “authenticationSessions”, “offlineSessions”, “clientSessions”, “offlineSessions”, “loginFailures” y “actionTokens” se distribuyen porque funcionan mejor que el caché replicado (consulte https://infinispan.org/docs /stable/user_guide/user_guide.html#which_cache_mode_should_i_use) porque solo tienes que replicar los datos a los propietarios.
- Los otros cambios propuestos por keycloak para su configuración HA predeterminada son distribuir el contenedor de caché “web” y “ejb” (y superior), y cambiar el caché de “hibernación” a un “caché de invalidación” (como un caché local pero con invalidación sincronización).
Creo que la configuración de la caché debe definirse como “caché distribuida” para cachés como “sesiones”, “authenticationSessions”, “offlineSessions”, “clientSessions”, “offlineClientSessions”, “loginFailures” y “actionTokens” (en lugar de “local “). Sin embargo, debido a que usa una tienda compartida remota, debe probarla para ver cómo funciona como dije antes.
Además, el caché llamado “trabajo” debe ser “caché replicado” y los demás (“claves”, “autorización”, “reinos” y “usuarios”) deben definirse como “caché local”.
En su clúster infinispan, puede definirlo como “caché distribuida” (o “caché replicada”).
Recuérdalo:
En una caché replicada, todos los nodos de un clúster contienen todas las claves, es decir, si existe una clave en un nodo, también existirá en todos los demás nodos. En una caché distribuida, se mantienen varias copias para proporcionar redundancia y tolerancia a fallas; sin embargo, esto suele ser mucho menor que el número de nodos en el clúster. Una caché distribuida proporciona un grado mucho mayor de escalabilidad que una caché replicada. Una caché distribuida también puede ubicar claves de forma transparente en un clúster y proporciona una caché L1 para un acceso de lectura local rápido del estado que se almacena de forma remota. Puede leer más en el capítulo correspondiente de la Guía del usuario.
Infinispan doc. ref: modo caché
Como dice la documentación de Keycloak (6.0):
Keycloak tiene dos tipos de cachés. Un tipo de caché se encuentra frente a la base de datos para disminuir la carga en la base de datos y disminuir los tiempos de respuesta generales al mantener los datos en la memoria. Los metadatos de dominio, cliente, rol y usuario se guardan en este tipo de caché. Este caché es un caché local. Las memorias caché locales no utilizan la replicación incluso si está en el clúster con más servidores Keycloak. En cambio, solo mantienen copias localmente y si la entrada se actualiza, se envía un mensaje de invalidación al resto del clúster y la entrada se desaloja. Hay un trabajo de caché replicado por separado, cuya tarea es enviar los mensajes de invalidación a todo el clúster sobre qué entradas deben desalojarse de los cachés locales. Esto reduce en gran medida el tráfico de la red, hace que las cosas sean eficientes y evita la transmisión de metadatos confidenciales a través del cable.
El segundo tipo de caché se encarga de administrar las sesiones de los usuarios, los tokens fuera de línea y realizar un seguimiento de las fallas de inicio de sesión para que el servidor pueda detectar la suplantación de identidad de contraseñas y otros ataques. Los datos almacenados en estos cachés son temporales, solo en la memoria, pero posiblemente se replican en todo el clúster.
Doc. Referencia: configuración de caché
Si quieres leer otro buen documento, puedes echar un vistazo a la sección “cross-dc” (modo cross-dc) especialmente a la sección “3.4.6 Infinispan cache” (infinispan cache)
Probé con Keycloak 6.0.1 e Infinispan 9.4.11.Final, aquí está mi configuración de prueba (basada en el archivo standalone-ha.xml).
Subsistema 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>
Fijaciones de enchufe de capa de llave:
<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>
Configuración del clúster 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>
PD: Cambie el atributo “propietarios” de 1 a su valor favorito.
Espero ser de ayuda.