如何基于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
相关产品推荐
相关产品推荐

