Gateway网关将登录用户信息传递给下游微服务(完整实现方案)

Gateway网关将登录用户信息传递给下游微服务(完整实现方案)

Gateway网关作为请求入口,传递登录用户信息的核心思路 是:网关层统一解析登录凭证(Token)→ 校验并获取完整用户信息 → 将用户信息写入HTTP请求头 → 下游微服务从请求头中读取并使用 ,全程保证请求链路的用户信息一致性,适配JWT、自定义Token等主流登录方式,以下是可直接落地的完整实现方案(含网关配置、代码实现、微服务接收、全局异常处理)。

一、核心实现流程

  1. 前端请求 :携带登录凭证(如token,放在请求头Authorization中,规范写法:Bearer {token})调用网关接口;

  2. 网关拦截 :通过全局过滤器(GlobalFilter) 拦截所有请求,提取并解析Token;

  3. 信息校验 :网关调用用户中心微服务直接解析JWT,校验Token有效性并获取用户核心信息(id、mobile、token等);

  4. 写入请求头 :将用户信息(JSON/拼接字符串/单独字段)写入自定义请求头 (如X-User-Info),传递给下游微服务;

  5. 微服务接收 :下游所有微服务通过拦截器/过滤器/AOP 统一从请求头中读取用户信息,封装为全局对象供业务使用;

  6. 异常处理:网关层统一处理Token过期、无效、缺失等异常,直接返回401/403,避免无效请求到达微服务。

二、前提准备(规范Token传递)

前端登录成功后,需将后端返回的tokenHTTP规范放在请求头中,网关统一从该位置提取,避免多端传递不一致:

  • 请求头键名Authorization(行业通用,推荐)

  • 值格式Bearer + 空格 + token(如Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

  • 前端请求示例(Axios):

    JavaScript 复制代码
    axios({
      url: '/train/main',
      method: 'get',
      headers: {
        'Authorization': `Bearer ${localStorage.getItem('token')}` // 从本地存储取token
      }
    })

三、网关层实现(核心:全局过滤器)

3.1 网关依赖(确保已引入,Spring Cloud Gateway)

XML 复制代码
<!-- Gateway核心依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 若用JWT解析,引入JWT依赖 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<!-- 服务调用(若需调用用户中心校验Token,引入OpenFeign) -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

3.2 核心:全局过滤器实现(二选一:JWT解析 / Feign调用用户中心)

Gateway网关通过GlobalFilter+Ordered实现全局请求拦截,无需修改原有路由配置,过滤器会对所有经过网关的请求生效。

方案1:直接解析JWT(推荐,性能更高,无需调用微服务)

适用于登录时生成JWT Token(用户信息已加密在Token中),网关直接解析Token获取用户信息,无需调用其他服务,性能最优。

Java 复制代码
package com.jagochan.gateway.filter;

import com.alibaba.fastjson2.JSON;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 网关全局过滤器:解析JWT Token,将用户信息写入请求头传递给下游微服务
 */
@Component
public class UserInfoTransferFilter implements GlobalFilter, Ordered {

    // 1. JWT配置(与登录生成Token时的密钥、过期时间保持一致)
    private static final String JWT_SECRET = "jagochan-train-2026-secret-key"; // 密钥,生产建议放配置中心
    private static final String TOKEN_PREFIX = "Bearer "; // Token前缀
    private static final String USER_INFO_HEADER = "X-User-Info"; // 传递用户信息的自定义请求头

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 2. 放行无需登录的接口(如登录页、静态资源、Swagger)
        String path = request.getPath().toString();
        if (path.contains("/train/login") || path.contains("/swagger-ui") 
                || path.contains("/v3/api-docs") || path.contains("/actuator")) {
            return chain.filter(exchange);
        }

        // 3. 提取请求头中的Token
        String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (!StringUtils.hasText(authHeader) || !authHeader.startsWith(TOKEN_PREFIX)) {
            // Token缺失/格式错误,返回401未授权
            return buildErrorResponse(response, HttpStatus.UNAUTHORIZED, "请先登录");
        }
        String token = authHeader.replace(TOKEN_PREFIX, "");

        try {
            // 4. 解析JWT Token,获取用户信息(Claims中存储了登录时放入的用户信息)
            SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode(JWT_SECRET), Jwts.SIG.HS256.key().build().getAlgorithm());
            Claims claims = Jwts.parser()
                    .verifyWith(secretKey)
                    .build()
                    .parseSignedClaims(token)
                    .getPayload();

            // 5. 提取核心用户信息(与登录时放入的字段一致)
            Long userId = claims.get("id", Long.class);
            String mobile = claims.get("mobile", String.class);
            String email = claims.get("email", String.class);

            // 6. 封装用户信息(JSON格式,方便下游微服务解析)
            Map<String, Object> userInfo = new HashMap<>();
            userInfo.put("id", userId);
            userInfo.put("mobile", mobile);
            userInfo.put("email", email);
            userInfo.put("token", token); // 可选:将token也传递下去,供微服务调用其他服务使用
            String userInfoJson = JSON.toJSONString(userInfo);

            // 7. 将用户信息写入请求头(Gateway需用mutate重构请求,因为request是不可变的)
            ServerHttpRequest newRequest = request.mutate()
                    .header(USER_INFO_HEADER, userInfoJson)
                    .build();
            // 8. 将重构后的请求传递给下游微服务
            return chain.filter(exchange.mutate().request(newRequest).build());

        } catch (Exception e) {
            // Token过期/无效,返回401
            return buildErrorResponse(response, HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录");
        }
    }

    /**
     * 构建统一的异常响应
     */
    private Mono<Void> buildErrorResponse(ServerHttpResponse response, HttpStatus status, String msg) {
        response.setStatusCode(status);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        Map<String, Object> error = new HashMap<>();
        error.put("code", status.value());
        error.put("msg", msg);
        String errorJson = JSON.toJSONString(error);
        DataBuffer buffer = response.bufferFactory().wrap(errorJson.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 过滤器执行顺序(数字越小,执行越早)
     * 设为-10,保证在网关路由过滤器之前执行,先解析用户信息再转发
     */
    @Override
    public int getOrder() {
        return -10;
    }
}
方案2:Feign调用用户中心校验Token(适用于非JWT场景)

若项目未使用JWT,Token为自定义随机字符串(用户信息存储在数据库/Redis中),网关通过OpenFeign调用用户中心微服务,校验Token并获取用户信息。

步骤1:网关编写Feign客户端(调用用户中心)
Java 复制代码
package com.jagochan.gateway.feign;

import com.jagochan.train.common.resp.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Map;

/**
 * 调用用户中心微服务,校验Token并获取用户信息
 */
@FeignClient(value = "user-service") // 用户中心微服务的服务名(注册中心中的名称)
public interface UserFeignClient {

    /**
     * 用户中心提供的Token校验接口
     * @param token 登录凭证
     * @return 包含用户信息的结果集
     */
    @GetMapping("/member/verifyToken")
    Result<Map<String, Object>> verifyToken(@RequestParam String token);
}
步骤2:修改过滤器,替换JWT解析为Feign调用
Java 复制代码
// 替换方案1中的try-catch块核心逻辑,其余代码不变
@Resource
private UserFeignClient userFeignClient;

try {
    // 4. 调用用户中心校验Token,获取用户信息
    Result<Map<String, Object>> result = userFeignClient.verifyToken(token);
    if (!result.isSuccess() || result.getData() == null) {
        return buildErrorResponse(response, HttpStatus.UNAUTHORIZED, result.getMsg());
    }
    Map<String, Object> userInfo = result.getData();

    // 5. 封装用户信息为JSON(用户中心返回的信息已包含id/mobile/email等)
    String userInfoJson = JSON.toJSONString(userInfo);

    // 6. 写入请求头,转发请求(与方案1一致)
    ServerHttpRequest newRequest = request.mutate()
            .header(USER_INFO_HEADER, userInfoJson)
            .build();
    return chain.filter(exchange.mutate().request(newRequest).build());

} catch (Exception e) {
    return buildErrorResponse(response, HttpStatus.UNAUTHORIZED, "登录验证失败,请重新登录");
}
步骤3:用户中心实现verifyToken接口
Java 复制代码
// 用户中心微服务的控制器
@RestController
@RequestMapping("/member")
public class MemberController {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 网关调用的Token校验接口
     * @param token 登录凭证
     * @return 用户信息
     */
    @GetMapping("/verifyToken")
    public Result<Map<String, Object>> verifyToken(@RequestParam String token) {
        // 从Redis中获取用户信息(登录时将token作为key,用户信息作为value存入Redis)
        String redisKey = "login:token:" + token;
        Map<String, Object> userInfo = (Map<String, Object>) redisTemplate.opsForValue().get(redisKey);
        if (userInfo == null) {
            return Result.fail("Token无效或已过期");
        }
        return Result.success(userInfo);
    }
}

3.3 网关跨域配置兼容(关键!避免请求头丢失)

若网关已配置跨域(CORS),需显式允许自定义请求头 X-User-Info ,否则浏览器会拦截该请求头,导致下游微服务无法获取用户信息,修改网关application.yml

YAML 复制代码
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOriginPatterns: "*"
            allowedHeaders: "*" # 允许所有请求头(包含自定义的X-User-Info)
            allowedMethods: "*"
            allowCredentials: true
            maxAge: 3600
        add-to-simple-url-handler-mapping: true

四、下游微服务实现(统一读取用户信息)

下游所有微服务无需重复解析Token ,直接从网关传递的X-User-Info请求头中读取用户信息,通过拦截器(Interceptor) 统一解析并封装为全局可访问的用户对象,业务层可直接注入使用,无需每次从请求头读取。

4.1 微服务通用依赖(确保已引入)

XML 复制代码
<!-- Web核心依赖(含拦截器支持) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- fastjson2(解析JSON格式的用户信息) -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.32</version>
</dependency>

4.2 步骤1:定义用户信息实体类(与网关传递的字段一致)

Java 复制代码
package com.jagochan.train.common.entity;

import lombok.Data;

/**
 * 全局用户信息实体(网关传递的用户信息)
 */
@Data
public class LoginUser {
    private Long id;         // 用户ID
    private String mobile;   // 手机号
    private String email;    // 邮箱
    private String token;    // 登录Token
}

4.3 步骤2:定义全局用户上下文(ThreadLocal存储,保证线程安全)

通过ThreadLocal存储当前请求的用户信息,同一请求的所有线程(业务层、服务层、DAO层)均可直接获取,线程安全且无侵入性。

Java 复制代码
package com.jagochan.train.common.context;

import com.jagochan.train.common.entity.LoginUser;

/**
 * 用户上下文:ThreadLocal存储当前登录用户信息
 */
public class UserContext {
    // ThreadLocal:每个线程独立存储,避免多线程数据混乱
    private static final ThreadLocal<LoginUser> USER_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 设置当前用户信息
     */
    public static void setLoginUser(LoginUser loginUser) {
        USER_THREAD_LOCAL.set(loginUser);
    }

    /**
     * 获取当前登录用户信息
     */
    public static LoginUser getLoginUser() {
        return USER_THREAD_LOCAL.get();
    }

    /**
     * 获取当前用户ID(常用,封装快捷方法)
     */
    public static Long getUserId() {
        LoginUser loginUser = getLoginUser();
        return loginUser == null ? null : loginUser.getId();
    }

    /**
     * 移除当前用户信息(防止内存泄漏)
     */
    public static void remove() {
        USER_THREAD_LOCAL.remove();
    }
}

4.4 步骤3:实现拦截器,解析请求头并设置用户上下文

Java 复制代码
package com.jagochan.train.member.interceptor;

import com.alibaba.fastjson2.JSON;
import com.jagochan.train.common.context.UserContext;
import com.jagochan.train.common.entity.LoginUser;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 微服务全局拦截器:解析网关传递的用户信息,设置到UserContext
 */
@Component
public class UserInfoInterceptor implements HandlerInterceptor {

    // 与网关定义的自定义请求头一致
    private static final String USER_INFO_HEADER = "X-User-Info";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 从请求头中获取用户信息JSON
        String userInfoJson = request.getHeader(USER_INFO_HEADER);
        if (userInfoJson != null && !userInfoJson.isEmpty()) {
            // 2. 解析JSON为LoginUser对象
            LoginUser loginUser = JSON.parseObject(userInfoJson, LoginUser.class);
            // 3. 设置到用户上下文(ThreadLocal)
            UserContext.setLoginUser(loginUser);
        }
        // 4. 放行请求
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 5. 请求结束后移除ThreadLocal中的数据,防止内存泄漏
        UserContext.remove();
    }
}

4.5 步骤4:注册拦截器,使其全局生效

Java 复制代码
package com.jagochan.train.member.config;

import com.jagochan.train.member.interceptor.UserInfoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

/**
 * Web配置:注册全局拦截器
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Resource
    private UserInfoInterceptor userInfoInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册用户信息拦截器,匹配所有请求
        registry.addInterceptor(userInfoInterceptor).addPathPatterns("/**");
    }
}

4.6 步骤5:业务层直接使用用户信息(无侵入,极简)

微服务的控制器、服务层、DAO层 均可直接通过UserContext获取当前登录用户信息,无需任何参数传递,示例:

Java 复制代码
package com.jagochan.train.member.controller;

import com.jagochan.train.common.context.UserContext;
import com.jagochan.train.common.entity.LoginUser;
import com.jagochan.train.common.resp.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/train/main")
public class MainController {

    /**
     * 业务接口:直接获取登录用户信息
     */
    @GetMapping("/info")
    public Result<LoginUser> getUserInfo() {
        // 1. 快捷获取当前用户ID
        Long userId = UserContext.getUserId();
        // 2. 获取完整用户信息
        LoginUser loginUser = UserContext.getLoginUser();
        return Result.success(loginUser);
    }
}

五、关键优化与避坑点

5.1 网关过滤器执行顺序(必对!)

  • 过滤器getOrder()返回负数 (如-10),保证在Gateway的路由过滤器、负载均衡过滤器之前执行,先解析用户信息再转发请求;

  • 若有多个全局过滤器,按order值从小到大执行,确保用户信息过滤器最先执行。

5.2 防止请求头中文/特殊字符乱码

网关传递的用户信息为JSON字符串,若包含中文(如昵称),需确保请求头编码为UTF-8,Gateway默认支持,微服务端无需额外配置(HttpServletRequest默认按UTF-8解析请求头)。

5.3 微服务ThreadLocal内存泄漏防护

  • 必须在afterCompletion中调用UserContext.remove(),因为Tomcat使用线程池,线程执行完后不会销毁,若不移除,ThreadLocal会持有对象引用,导致内存泄漏;

  • afterCompletion会在请求处理完成(包括异常) 后执行,确保无论请求成功与否,都会清理数据。

5.4 放行无需登录的接口(网关层必做!)

网关过滤器中必须放行登录接口、静态资源、Swagger、健康检查接口,否则这些接口会被拦截,返回401:

Java 复制代码
if (path.contains("/login") || path.contains("/swagger-ui") 
        || path.contains("/v3/api-docs") || path.contains("/actuator/health")) {
    return chain.filter(exchange);
}

5.5 生产环境JWT密钥安全

  • JWT的密钥(JWT_SECRET不可硬编码 ,生产环境放入Nacos/Apollo配置中心,通过@Value注入;

  • 密钥长度至少32位,使用随机字符串生成,避免被破解。

5.6 下游微服务无需再做登录校验

  • 所有请求必须经过网关,网关层已统一校验Token有效性,微服务端无需再校验Token ,直接使用UserContext的信息即可;

  • 若微服务需对外提供接口(绕过网关),需单独添加Token校验逻辑。

六、拓展场景:微服务之间调用传递用户信息

若下游微服务之间相互调用(如A微服务调用B微服务),需要传递当前登录用户信息,只需在Feign请求中添加请求头即可,实现Feign请求拦截器

Java 复制代码
package com.jagochan.train.common.feign;

import com.jagochan.train.common.context.UserContext;
import com.jagochan.train.common.entity.LoginUser;
import com.alibaba.fastjson2.JSON;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;

/**
 * Feign全局拦截器:微服务之间调用时,传递用户信息到请求头
 */
@Component
public class FeignUserInfoInterceptor implements RequestInterceptor {

    private static final String USER_INFO_HEADER = "X-User-Info";

    @Override
    public void apply(RequestTemplate template) {
        // 获取当前用户信息,写入Feign请求头
        LoginUser loginUser = UserContext.getLoginUser();
        if (loginUser != null) {
            template.header(USER_INFO_HEADER, JSON.toJSONString(loginUser));
        }
    }
}

所有微服务引入该公共拦截器后,相互调用时会自动传递用户信息,保证整个调用链路的用户信息一致性。

七、核心总结

  1. 核心方案 :网关全局过滤器 解析Token→获取用户信息→写入自定义请求头,下游微服务拦截器 解析请求头→ThreadLocal封装→业务层直接使用;

  2. 性能优选:使用JWT解析用户信息,无需调用微服务,网关层完成所有校验,性能最高;

  3. 无侵入性 :微服务通过UserContext全局获取用户信息,业务代码无需任何修改,符合开闭原则;

  4. 链路一致性:配合Feign拦截器,实现微服务之间调用的用户信息自动传递,全链路无感知;

  5. 统一异常:网关层统一处理Token相关异常,返回标准401响应,下游微服务无需重复处理。

该方案适配Spring Cloud Gateway主流使用场景,配置简单、扩展性强,生产环境可直接落地,完全解决网关向下游微服务传递登录用户信息的问题。

相关推荐
爱吃大芒果5 小时前
Flutter for OpenHarmony 实战: mango_shop 商品模块的列表渲染与下拉刷新功能
flutter·架构·dart
七夜zippoe5 小时前
NumPy高级:结构化数组与内存布局优化实战指南
python·架构·numpy·内存·视图
桂花很香,旭很美13 小时前
智能体技术架构:从分类、选型到落地
人工智能·架构
sxgzzn17 小时前
能源行业智能监测产品与技术架构解析
架构·数字孪生·无人机巡检
小邓吖18 小时前
自己做了一个工具网站
前端·分布式·后端·中间件·架构·golang
Java烘焙师19 小时前
架构师必备:灰度方案汇总
架构·数仓
王锋(oxwangfeng)21 小时前
企业出海网络架构与数据安全方案
网络·架构·自动驾驶
麦聪聊数据21 小时前
利用SQL2API模式重构微服务中的数据查询层
数据库·sql·低代码·微服务·架构
郝学胜-神的一滴1 天前
Python List操作:+、+=、extend的深度解析
开发语言·数据结构·python·程序人生·架构·list