You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot整合Keycloak多租户动态Realm配置最优方案问询

Keycloak多Realm动态校验实现方案

核心思路

默认的单Realm配置仅支持固定公钥验签,多租户场景下需要从请求的JWT中提取签发Realm标识,动态加载对应Realm的验签配置完成校验,无需修改现有业务逻辑。

实现步骤

1. 调整配置文件

移除application.yml中的固定realm配置,新增允许的租户Realm列表:

keycloak:
  auth-server-url: https://localhost:8443/auth
  ssl-required: external
  resource: app
  bearer-only: true
  use-resource-role-mappings: true
  # 新增允许的租户Realm列表,可根据业务扩展
  allowed-tenants: Realm1,Realm2

2. 自定义动态JWT解码器

替换Spring Security默认的单实例JwtDecoder,实现根据JWT签发者动态选择对应Realm的解码器:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.jwt.BadJwtException;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoders;
import org.springframework.security.web.SecurityFilterChain;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Configuration
@EnableWebSecurity
public class MultiTenantSecurityConfig {

    @Value("${keycloak.auth-server-url}")
    private String authServerUrl;

    @Value("${keycloak.allowed-tenants}")
    private List<String> allowedTenants;

    // 缓存不同Realm的JwtDecoder,避免重复创建实例
    private final ConcurrentHashMap<String, JwtDecoder> realmDecoderCache = new ConcurrentHashMap<>();

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwtConfigurer -> jwtConfigurer.decoder(dynamicJwtDecoder()))
            );
        return http.build();
    }

    private JwtDecoder dynamicJwtDecoder() {
        return token -> {
            // 1. 轻量解析JWT payload提取issuer(无需验签,生产环境可替换为JJWT等无验签解析工具降低开销)
            Jwt tempJwt;
            try {
                tempJwt = JwtDecoders.fromOidcIssuerLocation(
                    authServerUrl + "/realms/" + allowedTenants.get(0)
                ).decode(token);
            } catch (Exception e) {
                throw new BadJwtException("JWT格式非法");
            }
            String issuer = tempJwt.getIssuer().toString();
            String realm = issuer.substring(issuer.lastIndexOf("/") + 1);

            // 2. 校验Realm是否在允许的租户列表中
            if (!allowedTenants.contains(realm)) {
                throw new OAuth2AuthenticationException("非法租户Realm:" + realm);
            }

            // 3. 缓存中获取或创建对应Realm的解码器
            JwtDecoder decoder = realmDecoderCache.computeIfAbsent(realm, r ->
                JwtDecoders.fromOidcIssuerLocation(authServerUrl + "/realms/" + r)
            );
            return decoder.decode(token);
        };
    }
}

注意:生产环境建议使用JJWT等工具做无验签的JWT payload解析,避免临时解码的性能开销,同时可给realmDecoderCache添加过期策略,清理长期不用的Realm解码器实例。

3. 适配权限映射(可选)

如果需要保留原有的Keycloak角色、权限映射逻辑,自定义Converter<Jwt, AbstractAuthenticationToken>实现权限转换即可,逻辑与单Realm场景完全一致。

动态租户扩展(可选)

如果后续需要支持租户动态新增无需重启应用,可将allowed-tenants配置放到配置中心(Nacos/Apollo等),配合缓存清理逻辑即可实现租户热更新。


内容的提问来源于stack exchange,提问作者Lucho82

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 17:45:03