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

相关推荐
IT_陈寒1 小时前
Python线程池吞了异常还不告诉我,这谁顶得住啊
前端·人工智能·后端
小强库计算机毕业设计2 小时前
SpringBoot+Vue3 学生宿舍管理系统实战
java·spring boot·后端·vue·学生宿舍管理系统
小狼1832 小时前
实践6|SDD 实战:AI 写的规格被推翻了四次
后端·claude
篮框坏了2 小时前
"DeepSeek Harness 实测:一毛钱干三活,但默认配置是个坑"
后端·ai编程
jobBridge212 小时前
大模型到底是怎么"想"的?我把 Transformer 拆开,发现它其实是个"接词狂魔"
人工智能·后端·编程语言
唐青枫3 小时前
看懂内存地址之后,才算真正入门 Zig:指针、切片与实战
后端
朋克洛德的码农3 小时前
Go并发-sync包四剑客:Mutex、RWMutex、WaitGroup、Once-从入门到原理
开发语言·后端·golang
fulton3 小时前
为什么不用现成的开源工具?NovelOps与6大AI写作工具横向对比
后端
fulton3 小时前
3个月踩坑实录:从想法到270章规划,AI写长篇到底要花多少成本?
后端
fulton3 小时前
为什么AI写到20章就开始"复制粘贴"自己?创意扰动机制详解
后端