Keycloak 19.0.1自定义用户属性JWT缓存过期问题咨询
我使用Keycloak 19.0.1搭配自定义User Storage SPI从遗留数据库加载用户,同时通过该SPI为Keycloak用户模型添加自定义属性,这些属性会包含在调用/token接口签发的JWT令牌中。
目前调用/token接口时,Keycloak能正常签发包含自定义属性的JWT,但当遗留应用更新了数据库中的用户信息后,Keycloak签发的JWT仍携带过时的自定义属性值。我判断这是Keycloak的缓存问题,但没找到正确的配置项来禁用缓存,以确保JWT始终获取最新的自定义属性值。
Keycloak默认支持多种本地和分布式缓存,可通过XML文件配置。我用的是默认的Infinispan缓存模式,已经尝试过以下配置(Docker镜像代码见下方):
- 构建Keycloak时传递
kc.sh build --spi-user-cache-infinispan-enabled=false参数 - 构建阶段设置环境变量
KC_SPI_USER_CACHE_DEFAULT_ENABLED=false - 在用户联合中设置
NO_CACHE策略,但Keycloak 19版本中该选项存在bug,会导致自定义属性丢失,因此暂不可用
请问应该采用何种缓存配置,才能让Keycloak签发的JWT始终获取最新的自定义属性?是否需要通过XML文件自定义Infinispan配置?具体要修改哪些缓存配置项?
注意:手动失效该用户的所有会话后,Keycloak就能签发包含最新自定义属性的JWT。
FROM <private-/gradle:7.1.0-jdk11 AS build COPY --chown=gradle:gradle . /home/gradle/src WORKDIR /home/gradle/src RUN gradle build --no-daemon FROM <private-registry>/quay.io_keycloak/keycloak:19.0.1 as builder ENV KC_HEALTH_ENABLED=true ENV KC_METRICS_ENABLED=true ENV KC_DB=postgres ENV KC_HTTP_RELATIVE_PATH="/auth" ENV KC_HTTP_ENABLED=true ENV KC_HOSTNAME_STRICT_HTTPS=false ENV KC_HOSTNAME_STRICT=false ENV KC_TRANSACTION_XA_ENABLED=false ENV KC_SPI_USER_CACHE_DEFAULT_ENABLED=false COPY --from=build /home/gradle/src/user-storage-spi/build/libs/*.jar /opt/keycloak/providers/ COPY /conf/quarkus.properties /opt/keycloak/conf/quarkus.properties RUN /opt/keycloak/bin/kc.sh build --spi-user-cache-infinispan-enabled=false FROM <private-registry>/quay.io_keycloak/keycloak:19.0.1 COPY --from=builder /opt/keycloak/ /opt/keycloak/ WORKDIR /opt/keycloak ENV KC_HTTP_ENABLED=true ENV KC_HOSTNAME_STRICT_HTTPS=false ENV KC_HOSTNAME_STRICT=false ENV KC_TRANSACTION_XA_ENABLED=false ENV KC_SPI_USER_CACHE_DEFAULT_ENABLED=false ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
核心问题分析
你之前尝试的用户缓存禁用配置未生效,是因为Keycloak除了用户实体缓存外,还有会话缓存和用户会话缓存会存储已加载的用户属性——即使禁用了用户缓存,已存在的用户会话依然会保留旧属性。另外,User Storage SPI本身的缓存逻辑也可能影响属性刷新。
具体配置方案
1. 自定义Infinispan缓存配置(推荐)
通过XML文件调整users、userSessions、sessions这几个核心缓存的过期策略,确保旧数据快速失效:
- 在Keycloak的
conf目录下创建infinispan.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <infinispan xmlns="urn:infinispan:config:12.1"> <cache-container name="keycloak"> <!-- 禁用用户实体缓存,强制每次从数据库加载 --> <local-cache name="users"> <expiration lifespan="0" max-idle="0"/> </local-cache> <!-- 设置用户会话缓存1分钟过期,确保旧会话快速失效 --> <local-cache name="userSessions"> <expiration lifespan="60000" max-idle="60000"/> </local-cache> <!-- 同步调整全局会话缓存过期时间 --> <local-cache name="sessions"> <expiration lifespan="60000" max-idle="60000"/> </local-cache> </cache-container> </infinispan>
- 修改Dockerfile的builder阶段,将该文件复制到Keycloak配置目录:
COPY /conf/infinispan.xml /opt/keycloak/conf/
2. 补充SPI层面的缓存控制
在你的自定义User Storage SPI实现中,强制从数据库加载最新用户属性,避免依赖缓存:
@Override public UserModel getUserByUsername(String username, RealmModel realm) { // 直接从遗留数据库加载最新用户数据 UserModel user = loadUserFromLegacyDB(username); if (user != null) { // 强制更新Keycloak用户模型中的属性 updateUserAttributes(user); } return user; }
同时,在遗留系统更新用户后,可调用Keycloak Admin API主动失效该用户的所有会话:
curl -X POST "http://<keycloak-host>/auth/admin/realms/<realm>/users/<user-id>/logout" \ -H "Authorization: Bearer <admin-token>"
3. 修正环境变量配置
确保SPI的缓存策略为DEFAULT(避开有bug的NO_CACHE),结合Infinispan的过期配置实现属性刷新:
KC_SPI_USER_STORAGE_<你的SPI-ID>_CACHE_POLICY=DEFAULT
验证方法
- 更新遗留数据库中的用户自定义属性
- 调用
/token接口获取新JWT - 解码JWT检查自定义属性是否更新
- 若未更新,等待1分钟(缓存过期时间)后再次尝试
内容的提问来源于stack exchange,提问作者Juri Adam

