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

如何通过编程方式为Spring Cloud Gateway配置多Realm的Keycloak

问题描述

我正在构建一个Spring Cloud Gateway,已完成Keycloak安全验证集成且运行正常,但当前通过yml文件手动配置单个Realm,现需改为编程方式实现多Realm的动态配置。以下是当前使用的依赖、启动类代码、Security配置代码及yml配置:

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

启动类代码

@SpringBootApplication
@ComponentScan(basePackages = {"com", "com.b","com.auth","com.security"})
public class APIGatewayApplication {
   public static void main(String[] args) {
        SpringApplication.run(APIGatewayApplication.class, args);
    }
   @Bean
    public KeycloakConfigResolver keycloakConfigResolver() {
        return new PathBasedKeycloakConfigResolver();
    }
}

Security配置代码

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) {

        http
                .authorizeExchange()
                .anyExchange()
                .authenticated()
                .and()
                .oauth2Login(); // to redirect to oauth2 login page.

        return http.build();
    }

}

当前yml配置

spring:
  security:
    oauth2:
      client:
        provider:
          my-keycloak-provider:
            issuer-uri: http://localhost:8280/auth/realms/Default
        registration:
          keycloak-spring-gateway-client:
            provider: my-keycloak-provider
            client-id: Default
            client-secret: 8ZRUH62Pfhfde6uqasD8dfgdhvqWt03K6
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/app'
  main:
    web-application-type: reactive
  application:
    name: app
  cloud:
    gateway:
      default-filters:
        - TokenRelay

实现多Realm动态配置的方案

要实现多Realm的动态配置,核心是通过编程方式动态注册OAuth2客户端和Keycloak Realm配置,替代yml中的静态配置。以下是具体实现步骤:

1. 定义Realm配置实体类

创建实体类存储每个Realm的核心配置信息,后续可从数据库、配置中心等外部源加载:

public class RealmConfig {
    private String realmName;
    private String issuerUri;
    private String clientId;
    private String clientSecret;
    private String redirectUri;

    // 构造函数、getter、setter方法省略
}

2. 动态加载Realm配置

实现配置加载逻辑,模拟从外部源读取多个Realm的配置(实际场景可替换为数据库查询、配置中心拉取等):

@Component
public class RealmConfigLoader {
    public List<RealmConfig> loadAllRealms() {
        List<RealmConfig> realms = new ArrayList<>();
        // 加载默认Realm
        realms.add(new RealmConfig(
                "Default",
                "http://localhost:8280/auth/realms/Default",
                "Default",
                "8ZRUH62Pfhfde6uqasD8dfgdhvqWt03K6",
                "{baseUrl}/app"
        ));
        // 加载业务Realm
        realms.add(new RealmConfig(
                "Customer",
                "http://localhost:8280/auth/realms/Customer",
                "customer-gateway-client",
                "xYzwU73QgijklmnopQrStuvwxYz12345",
                "{baseUrl}/customer/app"
        ));
        return realms;
    }
}

3. 实现动态客户端注册仓库

自定义ReactiveClientRegistrationRepository,替换yml中的静态客户端注册配置:

@Component
public class DynamicClientRegistrationRepository implements ReactiveClientRegistrationRepository {

    private final Map<String, ClientRegistration> registrations = new ConcurrentHashMap<>();

    public DynamicClientRegistrationRepository(RealmConfigLoader configLoader) {
        List<RealmConfig> realmConfigs = configLoader.loadAllRealms();
        for (RealmConfig config : realmConfigs) {
            // 构建对应Realm的OAuth2客户端注册信息
            ClientRegistration registration = ClientRegistration.withRegistrationId("keycloak-" + config.getRealmName())
                    .clientId(config.getClientId())
                    .clientSecret(config.getClientSecret())
                    .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                    .redirectUri(config.getRedirectUri())
                    .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                    .authorizationUri(config.getIssuerUri() + "/protocol/openid-connect/auth")
                    .tokenUri(config.getIssuerUri() + "/protocol/openid-connect/token")
                    .userInfoUri(config.getIssuerUri() + "/protocol/openid-connect/userinfo")
                    .jwkSetUri(config.getIssuerUri() + "/protocol/openid-connect/certs")
                    .userNameAttributeName(IdTokenClaimNames.SUB)
                    .clientName("Keycloak " + config.getRealmName())
                    .build();
            registrations.put(registration.getRegistrationId(), registration);
        }
    }

    @Override
    public Mono<ClientRegistration> findByRegistrationId(String registrationId) {
        return Mono.justOrEmpty(registrations.get(registrationId));
    }
}

4. 配置授权客户端服务

注入自定义的客户端注册仓库,实现动态授权客户端管理:

@Bean
public ReactiveOAuth2AuthorizedClientService authorizedClientService(
        ReactiveClientRegistrationRepository clientRegistrationRepository) {
    return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
}

5. 调整Security配置

修改Security配置,使其使用动态客户端注册信息,并支持多Realm的OAuth2登录:

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
                                                           ReactiveClientRegistrationRepository clientRegistrationRepository,
                                                           ReactiveOAuth2AuthorizedClientService authorizedClientService) {
        OAuth2LoginSpec oauth2Login = http.oauth2Login()
                .clientRegistrationRepository(clientRegistrationRepository)
                .authorizedClientService(authorizedClientService);

        http
                .authorizeExchange()
                .anyExchange().authenticated()
                .and()
                .oauth2Login(oauth2Login);

        return http.build();
    }
}

6. 自定义PathBasedKeycloakConfigResolver(可选)

如果需要根据请求路径匹配对应Realm,自定义Resolver实现路径到Realm的映射:

public class CustomPathBasedKeycloakConfigResolver extends PathBasedKeycloakConfigResolver {
    private final RealmConfigLoader configLoader;
    private final Map<String, KeycloakDeployment> deploymentMap = new ConcurrentHashMap<>();

    public CustomPathBasedKeycloakConfigResolver(RealmConfigLoader configLoader) {
        this.configLoader = configLoader;
        loadAllRealmDeployments();
    }

    private void loadAllRealmDeployments() {
        List<RealmConfig> realmConfigs = configLoader.loadAllRealms();
        for (RealmConfig config : realmConfigs) {
            KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config.getIssuerUri());
            deployment.setResourceName(config.getClientId());
            deployment.setClientSecret(config.getClientSecret());
            deploymentMap.put(config.getRealmName(), deployment);
        }
    }

    @Override
    public KeycloakDeployment resolve(HttpFacade.Request request) {
        // 从请求路径提取Realm名称,示例:路径/realm/Customer/api/xxx中提取Customer
        String path = request.getURI();
        String realmName = extractRealmFromPath(path);
        return deploymentMap.getOrDefault(realmName, deploymentMap.get("Default"));
    }

    private String extractRealmFromPath(String path) {
        String[] segments = path.split("/");
        for (int i = 0; i < segments.length; i++) {
            if ("realm".equals(segments[i]) && i + 1 < segments.length) {
                return segments[i + 1];
            }
        }
        return "Default";
    }
}

然后在启动类中替换原有的KeycloakConfigResolver Bean:

@Bean
public KeycloakConfigResolver keycloakConfigResolver(RealmConfigLoader configLoader) {
    return new CustomPathBasedKeycloakConfigResolver(configLoader);
}

7. 清理yml静态配置

移除yml中spring.security.oauth2.client下的provider和registration配置,避免与编程式配置冲突。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 19:25:50