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

如何在Spring Cloud Gateway过滤器中从Keycloak获取JWT并实现路由控制

Spring Cloud Gateway集成Keycloak实现角色校验与路由控制

针对你替换登录服务为Keycloak后的需求,以下是具体的集成改造步骤:

1. 更新依赖配置

移除原有自定义登录相关依赖,添加OAuth2资源服务器依赖,让Spring Security自动处理Keycloak JWT的校验:

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
</dependencies>

2. 配置Keycloak资源服务器参数

在application.yml中配置Keycloak的Realm地址,Spring Security会自动拉取公钥完成JWT签名验证:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://{keycloak-host}:{port}/realms/{你的Realm名称}
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

3. 自定义JWT权限转换器(可选)

Keycloak的角色默认存储在realm_access.roles或resource_access.{client-id}.roles字段中,需要将其转换为Spring Security的GrantedAuthority格式:

@Component
public class KeycloakJwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        Collection<String> roles = new ArrayList<>();
        // 提取Realm级角色
        Map<String, Object> realmAccess = jwt.getClaim("realm_access");
        if (realmAccess != null) {
            roles.addAll((Collection<String>) realmAccess.get("roles"));
        }
        // 可选:提取客户端级角色
        // Map<String, Object> clientAccess = jwt.getClaim("resource_access");
        // if (clientAccess != null) {
        //     Map<String, Object> clientRoles = (Map<String, Object>) clientAccess.get("你的客户端ID");
        //     if (clientRoles != null) {
        //         roles.addAll((Collection<String>) clientRoles.get("roles"));
        //     }
        // }
        // 转换为Spring Security权限格式,前缀ROLE_是约定俗成的规范
        Collection<GrantedAuthority> authorities = roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList());
        return new JwtAuthenticationToken(jwt, authorities);
    }
}

4. 改造自定义RouteFilter

在原有Filter中,通过ServerWebExchange获取认证上下文,完成角色校验、请求头填充和路由转发:

@Component
public class CustomRouteFilter implements GatewayFilter, Ordered {

    private static final String ERROR_ROUTE_ID = "error-page-service";
    // 配置目标服务与所需角色的映射关系
    private final Map<String, String> SERVICE_ROLE_MAP = Map.of(
            "service-a", "ROLE_SERVICE_A",
            "service-b", "ROLE_SERVICE_B"
    );

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return exchange.getPrincipal()
                .cast(JwtAuthenticationToken.class)
                .flatMap(authToken -> {
                    // 获取当前路由对应的服务ID
                    Route currentRoute = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
                    String serviceId = currentRoute.getId();

                    // 校验是否拥有对应服务的角色
                    String requiredRole = SERVICE_ROLE_MAP.get(serviceId);
                    if (requiredRole == null || authToken.getAuthorities().stream()
                            .noneMatch(auth -> auth.getAuthority().equals(requiredRole))) {
                        // 无权限,替换路由为错误页服务
                        exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, buildErrorRoute(exchange));
                        return chain.filter(exchange);
                    }

                    // 填充用户ID和角色到请求头,供后端服务使用
                    String userId = authToken.getToken().getSubject();
                    String roles = authToken.getAuthorities().stream()
                            .map(GrantedAuthority::getAuthority)
                            .collect(Collectors.joining(","));
                    exchange.getRequest().mutate()
                            .header("X-User-ID", userId)
                            .header("X-User-Roles", roles);

                    // 权限校验通过,继续转发到目标服务
                    return chain.filter(exchange);
                })
                .onErrorResume(throwable -> {
                    // 处理未认证(JWT无效或缺失)的情况,转发到错误页
                    exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, buildErrorRoute(exchange));
                    return chain.filter(exchange);
                });
    }

    private Route buildErrorRoute(ServerWebExchange exchange) {
        // 构建错误页服务的路由,或从路由配置中获取已定义的错误路由
        return Route.async()
                .id(ERROR_ROUTE_ID)
                .uri("lb://error-page-service")
                .build();
    }

    @Override
    public int getOrder() {
        // 设置执行顺序,确保在Spring Security的认证Filter之后执行
        return SecurityWebFiltersOrder.AUTHORIZATION.getOrder() + 1;
    }
}

5. 配置Spring Security规则

确保非公开接口都需要JWT认证,同时放行登录、健康检查等接口:

@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, KeycloakJwtAuthConverter authConverter) {
        http
                .authorizeExchange(exchanges -> exchanges
                        .pathMatchers("/login", "/actuator/health").permitAll()
                        .anyExchange().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt.jwtAuthenticationConverter(authConverter))
                )
                .csrf(ServerHttpSecurity.CsrfSpec::disable);
        return http.build();
    }
}

6. 路由绑定Filter

在application.yml中为目标服务路由绑定自定义Filter:

spring:
  cloud:
    gateway:
      routes:
        - id: service-a
          uri: lb://service-a
          predicates:
            - Path=/service-a/**
          filters:
            - CustomRouteFilter
        - id: service-b
          uri: lb://service-b
          predicates:
            - Path=/service-b/**
          filters:
            - CustomRouteFilter
        - id: error-page-service
          uri: lb://error-page-service
          predicates:
            - Path=/error/**

关键说明

  • JWT的校验由Spring Security OAuth2资源服务器自动完成,无需手动解析签名,避免重复造轮子。
  • 角色映射关系可以根据实际业务调整,比如从配置文件读取,避免硬编码。
  • 如果需要更细粒度的权限控制,可以扩展Filter逻辑,比如校验接口级别的角色。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:40:58