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

Spring Cloud Gateway如何拦截与Keycloak IDP的底层请求/响应?

实现建议:拦截Keycloak IDP响应提取字段

针对Webflux版Spring Cloud Gateway对接Keycloak OAuth2时提取IDP响应字段的需求,推荐以下几种基于Spring Security原生扩展点的实现方案:


方案1:自定义OAuth2LoginAuthenticationConverter

该方案直接拦截Keycloak的Token响应转换流程,适合提取Token响应Payload或ID Token中的自定义字段:

  1. 继承默认转换器,重写字段提取逻辑
@Component
public class CustomKeycloakAuthConverter extends OAuth2LoginAuthenticationConverter {

    @Override
    protected OAuth2LoginAuthenticationToken extractAuthentication(Map<String, Object> tokenParams,
                                                                  OAuth2AuthorizationRequest authRequest,
                                                                  OAuth2AccessTokenResponse tokenResponse) {
        // 先执行框架默认的认证转换逻辑
        OAuth2LoginAuthenticationToken authToken = super.extractAuthentication(tokenParams, authRequest, tokenResponse);
        
        // 从Token响应Payload提取目标字段(示例:自定义字段custom_field)
        Object customField = tokenParams.get("custom_field");
        
        // 若字段在ID Token的Claims中,可从JWT解析提取
        if (tokenResponse.getAccessToken() instanceof Jwt jwtToken) {
            Object customClaim = jwtToken.getClaims().get("custom_claim");
            // 将提取的字段存入Authentication的Details,后续可从上下文获取
            authToken.setDetails(Map.of("custom_field", customField, "custom_claim", customClaim));
        }
        
        return authToken;
    }
}
  1. 配置自定义转换器到Security链
@Configuration
public class SecurityConfig {

    @Autowired
    private CustomKeycloakAuthConverter customConverter;

    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
            .oauth2Login(oauth2 -> oauth2.authenticationConverter(customConverter));
        return http.build();
    }
}

方案2:自定义ReactiveOAuth2UserService

如果目标字段在Keycloak的UserInfo响应中,可通过扩展用户信息加载逻辑实现提取:

  1. 实现自定义用户服务,拦截UserInfo响应
@Component
public class CustomKeycloakUserService implements ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> {

    private final DefaultReactiveOAuth2UserService delegate = new DefaultReactiveOAuth2UserService();

    @Override
    public Mono<OAuth2User> loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        return delegate.loadUser(userRequest)
            .map(oAuth2User -> {
                // 从UserInfo响应的属性中提取目标字段
                Object customField = oAuth2User.getAttributes().get("custom_field");
                
                // 包装自定义OAuth2User,或直接将字段存入上下文
                return new DefaultOAuth2User(
                    oAuth2User.getAuthorities(),
                    oAuth2User.getAttributes(),
                    oAuth2User.getNameAttributeKey()
                ) {
                    @Override
                    public Map<String, Object> getAttributes() {
                        Map<String, Object> attrs = new HashMap<>(super.getAttributes());
                        attrs.put("processed_custom_field", customField);
                        return attrs;
                    }
                };
            });
    }
}
  1. 配置自定义用户服务到Security链
@Configuration
public class SecurityConfig {

    @Autowired
    private CustomKeycloakUserService customUserService;

    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
            .oauth2Login(oauth2 -> oauth2.userService(customUserService));
        return http.build();
    }
}

方案3:网关层面拦截回调请求

若需要在SCG网关层独立处理回调响应,可添加自定义GatewayFilter:

@Component
public class CustomCallbackFilter implements GatewayFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();
        // 匹配Keycloak回调路径(需替换为实际的registrationId)
        if (path.startsWith("/login/oauth2/code/keycloak")) {
            return chain.filter(exchange)
                .doOnSuccess(aVoid -> {
                    // 登录成功后从认证上下文提取字段,存入Spring Session
                    exchange.getPrincipal()
                        .filter(p -> p instanceof OAuth2LoginAuthenticationToken)
                        .cast(OAuth2LoginAuthenticationToken.class)
                        .subscribe(auth -> {
                            Object customField = auth.getDetails();
                            exchange.getSession().subscribe(session -> {
                                session.getAttributes().put("custom_field", customField);
                            });
                        });
                });
        }
        return chain.filter(exchange);
    }
}

在路由配置中绑定该Filter:

spring:
  cloud:
    gateway:
      routes:
        - id: keycloak-callback
          uri: no://op
          predicates:
            - Path=/login/oauth2/code/keycloak
          filters:
            - CustomCallbackFilter

方案选择建议

  • 优先选择方案1或方案2:基于Spring Security原生扩展点,逻辑更贴合框架认证流程,无需额外网关路由配置
  • 方案3适合需要在网关层独立处理字段、不侵入认证逻辑的场景

内容的提问来源于stack exchange,提问作者diego.gazzola

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 20:50:32