在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 认证和用户信息传递机制。这种架构确保了安全逻辑的集中管理,同时让业务微服务能专注于核心功能。

相关推荐
盘古开天16661 天前
从零开始:如何搭建你的第一个简单的Flask网站
后端·python·flask
用户21411832636021 天前
Claude Skills 从零到一:手把手打造专属公众号文风生成器,10 分钟搞定 AI 技能定制
后端
追逐时光者1 天前
C#/.NET/.NET Core技术前沿周刊 | 第 60 期(2025年11.1-11.9)
后端·.net
码上成长1 天前
GraphQL:让前端自己决定要什么数据
前端·后端·graphql
码事漫谈1 天前
C++双向链表删除操作:由浅入深完全指南
后端
码事漫谈1 天前
软件生产的“高速公路网”:深入浅出理解CI/CD的核心流程
后端
Moonbit1 天前
MGPIC 初赛提交倒计时 4 天!
后端·算法·编程语言
程序定小飞1 天前
基于springboot的作业管理系统设计与实现
java·开发语言·spring boot·后端·spring
程序员小假1 天前
我们来说一下 Mybatis 的缓存机制
java·后端
沙虫一号1 天前
线上python问题排查思路
后端·python