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

如何基于bearer-only模式配置Spring Cloud Gateway完成Keycloak令牌校验

Spring Cloud Gateway 网关层统一校验Keycloak Bearer Token 实现方案

你要在网关层做令牌校验的方案完全可行,核心是要适配Spring Cloud Gateway的响应式架构,大部分运行失败的原因是误用了Servlet体系的OAuth2资源服务器配置。

1. 依赖引入

注意不要引入spring-boot-starter-web依赖,会和网关的WebFlux响应式架构冲突,这是最常见的启动失败原因
Maven pom.xml 核心依赖:

<!-- 网关核心依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 响应式OAuth2资源服务器依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

2. 配置文件编写

application.yml 配置示例:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          # Keycloak对应Realm的JWKS地址,Keycloak 17+版本不带/auth前缀,低于17的版本需要在/realms前加/auth
          jwk-set-uri: http://{Keycloak服务地址}/realms/{你的Realm名称}/protocol/openid-connect/certs
          # 可选配置,开启issuer字段校验,值和Keycloak Realm设置的issuer保持一致
          issuer-uri: http://{Keycloak服务地址}/realms/{你的Realm名称}
  cloud:
    gateway:
      # 你的后端微服务路由配置,示例如下
      routes:
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/order/**
      # 可选配置:默认把Authorization头透传给下游服务,如果不需要可以手动移除

3. 安全配置类编写

网关是WebFlux响应式架构,必须使用响应式安全配置,不要使用Servlet体系的配置:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
            // 前后端分离API场景禁用CSRF防护
            .csrf(ServerHttpSecurity.CsrfSpec::disable)
            .authorizeExchange(exchange -> exchange
                // 配置公开访问的接口路径,比如健康检查、公开接口等
                .pathMatchers("/actuator/health", "/public/**").permitAll()
                // 其余所有请求必须携带有效令牌
                .anyExchange().authenticated()
            )
            // 开启JWT格式的Bearer Token校验
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
        return http.build();
    }
}

注意不要使用@EnableWebSecurity注解和普通的SecurityFilterChain,这两个都是Servlet体系的组件,不适用于WebFlux架构

4. 可选扩展:用户信息透传

如果需要把JWT中解析出的用户信息传给下游微服务,不需要下游再次校验令牌,可以添加全局过滤器实现:

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class UserInfoRelayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return exchange.getPrincipal()
                .filter(JwtAuthenticationToken.class::isInstance)
                .cast(JwtAuthenticationToken.class)
                .map(auth -> {
                    Jwt jwt = auth.getToken();
                    String userId = jwt.getSubject();
                    String username = jwt.getClaimAsString("preferred_username");
                    // 将用户信息放入请求头透传给下游
                    ServerWebExchange modifiedExchange = exchange.mutate()
                            .request(req -> req
                                    .header("X-User-Id", userId)
                                    .header("X-Username", username)
                            )
                            .build();
                    return modifiedExchange;
                })
                .defaultIfEmpty(exchange)
                .flatMap(chain::filter);
    }

    @Override
    public int getOrder() {
        // 优先级低于认证过滤器,确保认证完成后再执行
        return -10;
    }
}

常见故障排查

  • 启动报错类不存在/循环依赖:检查项目中是否引入了spring-boot-starter-web,移除即可
  • 所有请求返回401:检查jwk-set-uri配置是否正确,令牌是否为对应Realm签发、是否过期
  • 安全配置不生效:检查是否加了@EnableWebFluxSecurity注解,返回的Bean是否为SecurityWebFilterChain类型

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 22:27:02