在Spring Cloud Gateway中如何具体实现JWT验证和用户信息提取?

在 Spring Cloud Gateway 中实现 JWT 验证和用户信息提取,核心思路是在网关层通过全局过滤器拦截请求,统一完成认证,再将解析出的用户信息传递给下游微服务。这样做既能保障安全,又能避免每个微服务重复处理认证逻辑。

下面我们通过一个具体的流程,并结合关键代码示例来详细说明如何实现。

🚀 核心实现步骤

整个实现流程主要包含网关层的认证过滤器和微服务层的用户信息接收两部分,其核心交互与数据流转如下图所示:

💻 关键代码实现

1. 网关层:创建 JWT 全局认证过滤器

这是实现的核心,负责校验令牌并传递用户信息。

java 复制代码
@Component
@RequiredArgsConstructor
public class JwtAuthGlobalFilter implements GlobalFilter, Ordered {

    private final JwtUtils jwtUtils; // 自定义的JWT工具类
    private final AuthProperties authProperties; // 配置类,存放排除路径
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().toString();

        // 1. 检查是否为无需认证的路径(如登录接口)
        if (isExclude(path)) {
            return chain.filter(exchange);
        }

        // 2. 从Authorization请求头中提取JWT
        String token = extractToken(request.getHeaders());
        if (token == null) {
            return unauthenticated(exchange, "Missing token");
        }

        try {
            // 3. 验证并解析JWT
            Claims claims = jwtUtils.parseToken(token);
            if (jwtUtils.isTokenExpired(token)) {
                return unauthenticated(exchange, "Token expired");
            }

            // 4. 提取用户信息,并添加到新的请求头中,传递给下游服务
            String userId = claims.getSubject();
            String roles = claims.get("roles", String.class);
            
            ServerWebExchange mutatedExchange = exchange.mutate().request(originalRequest -> 
                originalRequest.header("X-User-Id", userId)
                              .header("X-User-Roles", roles)
            ).build();

            // 5. 放行请求
            return chain.filter(mutatedExchange);

        } catch (Exception e) {
            return unauthenticated(exchange, "Invalid token");
        }
    }

    private boolean isExclude(String path) {
        return authProperties.getExcludePaths().stream()
                .anyMatch(pattern -> antPathMatcher.match(pattern, path));
    }

    private String extractToken(HttpHeaders headers) {
        String authHeader = headers.getFirst(HttpHeaders.AUTHORIZATION);
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }

    private Mono<Void> unauthenticated(ServerWebExchange exchange, String message) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        DataBuffer buffer = exchange.getResponse().bufferFactory()
                .wrap(String.format("{"code": 401, "msg": "%s"}", message).getBytes());
        return exchange.getResponse().writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -1; // 设置较高的优先级
    }
}

代码主要参考了和中的实现思路。

2. 微服务层:使用拦截器接收用户信息

下游微服务需要接收并保存用户信息,方便业务代码使用。

a. 创建用户上下文(UserContext)​

使用 ThreadLocal确保每个请求的用户信息线程隔离。

typescript 复制代码
public class UserContext {
    private static final ThreadLocal<Map<String, String>> CONTEXT = new ThreadLocal<>();

    public static void setUserInfo(Map<String, String> userInfo) {
        CONTEXT.set(userInfo);
    }

    public static String getUserId() {
        Map<String, String> userInfo = CONTEXT.get();
        return userInfo != null ? userInfo.get("userId") : null;
    }

    public static void clear() {
        CONTEXT.remove();
    }
}

代码主要参考了和中的实现思路。

b. 创建用户信息拦截器

typescript 复制代码
@Component
public class UserInfoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 从网关添加的请求头中获取用户信息
        String userId = request.getHeader("X-User-Id");
        String roles = request.getHeader("X-User-Roles");

        if (userId != null) {
            Map<String, String> userInfo = new HashMap<>();
            userInfo.put("userId", userId);
            userInfo.put("roles", roles);
            // 将用户信息存入上下文
            UserContext.setUserInfo(userInfo);
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 请求完成后务必清理ThreadLocal,防止内存泄漏
        UserContext.clear();
    }
}

代码主要参考了和中的实现思路。

c. 注册拦截器

typescript 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private UserInfoInterceptor userInfoInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userInfoInterceptor).addPathPatterns("/api/**");
    }
}

⚙️ 配置说明

application.yml中配置排除路径和路由规则。

yaml 复制代码
auth:
  exclude-paths:
    - "/api/auth/login"
    - "/api/public/**"

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1

配置思路参考了和。

⚠️ 重要实践建议

  1. 安全第一 :务必使用 HTTPS 来传输 JWT。JWT 的签名密钥要有足够的强度,并定期更换。
  2. 性能优化:对于高并发场景,可以考虑在网关层对 JWT 的解析结果进行短期缓存(如使用 Caffeine),避免重复解析。
  3. 令牌管理:为 JWT 设置合理的过期时间(如 1-2 小时)。对于需要主动撤销令牌的场景(如用户退出登录),可以结合 Redis 维护一个短期的令牌黑名单。
  4. 清晰界定职责 :网关负责认证 (Authentication,即"你是谁"),微服务负责授权(Authorization,即"你能干什么")。权限判断逻辑建议放在微服务内。

💎 总结

通过以上步骤,你就可以在 Spring Cloud Gateway 中构建一个统一、安全、高效的 JWT 认证和用户信息传递机制。这种架构确保了安全逻辑的集中管理,同时让业务微服务能专注于核心功能。

相关推荐
星辰徐哥2 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥2 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约2 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee2 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐2 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs2 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐2 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司2 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
码农阿豪2 小时前
从零到一:Spring Boot快速接入金仓数据库实战
数据库·spring boot·后端
追逐时光者2 小时前
一个基于 .NET 与 Avalonia 构建、面向 TrinityCore 的开源 WoW 数据库编辑器
后端·.net