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

相关推荐
Tech_Lin2 小时前
前端工作实战:如何在vite中配置代理解决跨域问题
前端·后端
间彧2 小时前
SecurityContext在分布式系统(如微服务)中如何传递?有哪些常见方案?
后端
孤廖3 小时前
C++ 模板再升级:非类型参数、特化技巧(含全特化与偏特化)、分离编译破解
linux·服务器·开发语言·c++·人工智能·后端·深度学习
林希_Rachel_傻希希3 小时前
Express 入门全指南:从 0 搭建你的第一个 Node Web 服务器
前端·后端·node.js
oak隔壁找我3 小时前
Java 使用技巧与最佳实践
java·后端
oak隔壁找我3 小时前
SpringMVC 使用技巧与最佳实践
java·后端
oak隔壁找我3 小时前
Spring 框架使用技巧与最佳实践
java·后端
白衣鸽子3 小时前
MySql数据库同步技术:构建高可用架构的基石
数据库·后端