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

