核心内容摘要
ASS字幕批量转换与优化完全指南:从问题诊断到特效保留的工程化解决方案
Gateway网关将登录用户信息传递给下游微服务完整实现方案
核心实现流程
前提准备规范Token传递
网关层实现核心全局过滤器
1 网关依赖确保已引入Spring Cloud Gateway
2 核心全局过滤器实现二选一JWT解析 / Feign调用用户中心方案1直接解析JWT推荐性能更高无需调用微服务方案2Feign调用用户中心校验Token适用于非JWT场景步骤1网关编写Feign客户端调用用户中心步骤2修改过滤器替换JWT解析为Feign调用步骤3用户中心实现verifyToken接口
3 网关跨域配置兼容关键避免请求头丢失
下游微服务实现统一读取用户信息
1 微服务通用依赖确保已引入
2 步骤1定义用户信息实体类与网关传递的字段一致
3 步骤2定义全局用户上下文ThreadLocal存储保证线程安全
4 步骤3实现拦截器解析请求头并设置用户上下文
5 步骤4注册拦截器使其全局生效
6 步骤5业务层直接使用用户信息无侵入极简
关键优化与避坑点
1 网关过滤器执行顺序必对
2 防止请求头中文/特殊字符乱码
3 微服务ThreadLocal内存泄漏防护
4 放行无需登录的接口网关层必做
5 生产环境JWT密钥安全
6 下游微服务无需再做登录校验
拓展场景微服务之间调用传递用户信息
核心
总结Gateway网关作为请求入口传递登录用户信息的核心思路是网关层统一解析登录凭证Token→ 校验并获取完整用户信息 → 将用户信息写入HTTP请求头 → 下游微服务从请求头中读取并使用全程保证请求链路的用户信息一致性适配JWT、自定义Token等主流登录方式以下是可直接落地的完整实现方案含网关配置、代码实现、微服务接收、全局异常处理。
核心实现流程前端请求携带登录凭证如token放在请求头Authorization中规范写法Bearer {token}调用网关接口网关拦截通过全局过滤器GlobalFilter拦截所有请求提取并解析Token信息校验网关调用用户中心微服务或直接解析JWT校验Token有效性并获取用户核心信息id、mobile、token等写入请求头将用户信息JSON/拼接字符串/单独字段写入自定义请求头如X-User-Info传递给下游微服务微服务接收下游所有微服务通过拦截器/过滤器/AOP统一从请求头中读取用户信息封装为全局对象供业务使用异常处理网关层统一处理Token过期、无效、缺失等异常直接返回401/403避免无效请求到达微服务。
前提准备规范Token传递前端登录成功后需将后端返回的token按HTTP规范放在请求头中网关统一从该位置提取避免多端传递不一致请求头键名Authorization行业通用推荐值格式Bearer 空格 token如Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ
..前端请求示例Axiosaxios({ url: /train/main, method: get, headers: { Authorization: Bearer ${localStorage.getItem(token)} // 从本地存储取token } })
网关层实现核心全局过滤器
1 网关依赖确保已引入Spring Cloud Gateway!-- Gateway核心依赖 -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency !-- 若用JWT解析引入JWT依赖 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version
0.
1
5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version
0.
1
5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version
0.
1
5/version scoperuntime/scope /dependency !-- 服务调用若需调用用户中心校验Token引入OpenFeign -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency
2 核心全局过滤器实现二选一JWT解析 / Feign调用用户中心Gateway网关通过GlobalFilterOrdered实现全局请求拦截无需修改原有路由配置过滤器会对所有经过网关的请求生效。
方案1直接解析JWT推荐性能更高无需调用微服务适用于登录时生成JWT Token用户信息已加密在Token中网关直接解析Token获取用户信息无需调用其他服务性能最优。
package com.jagochan.gateway.filter; import com.alibaba.fastjson
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 { //
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 MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request exchange.getRequest(); ServerHttpResponse response exchange.getResponse(); //
放行无需登录的接口如登录页、静态资源、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); } //
提取请求头中的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 { //
解析JWT Token获取用户信息Claims中存储了登录时放入的用户信息 SecretKey secretKey new SecretKeySpec(Base
getDecoder().decode(JWT_SECRET), Jwts.SIG.HS
key().build().getAlgorithm()); Claims claims Jwts.parser() .verifyWith(secretKey) .build() .parseSignedClaims(token) .getPayload(); //
提取核心用户信息与登录时放入的字段一致 Long userId claims.get(id, Long.class); String mobile claims.get(mobile, String.class); String email claims.get(email, String.class); //
封装用户信息JSON格式方便下游微服务解析 MapString, 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); //
将用户信息写入请求头Gateway需用mutate重构请求因为request是不可变的 ServerHttpRequest newRequest request.mutate() .header(USER_INFO_HEADER, userInfoJson) .build(); //
将重构后的请求传递给下游微服务 return chain.filter(exchange.mutate().request(newRequest).build()); } catch (Exception e) { // Token过期/无效返回401 return buildErrorResponse(response, HttpStatus.UNAUTHORIZED, 登录已过期请重新登录); } } /** * 构建统一的异常响应 */ private MonoVoid buildErrorResponse(ServerHttpResponse response, HttpStatus status, String msg) { response.setStatusCode(status); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); MapString, 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_
); return response.writeWith(Mono.just(buffer)); } /** * 过滤器执行顺序数字越小执行越早 * 设为-10保证在网关路由过滤器之前执行先解析用户信息再转发 */ Override public int getOrder() { return -10; } }方案2Feign调用用户中心校验Token适用于非JWT场景若项目未使用JWTToken为自定义随机字符串用户信息存储在数据库/Redis中网关通过OpenFeign调用用户中心微服务校验Token并获取用户信息。
步骤1网关编写Feign客户端调用用户中心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) ResultMapString, Object verifyToken(RequestParam String token); }步骤2修改过滤器替换JWT解析为Feign调用// 替换方案1中的try-catch块核心逻辑其余代码不变 Resource private UserFeignClient userFeignClient; try { //
调用用户中心校验Token获取用户信息 ResultMapString, Object result userFeignClient.verifyToken(token); if (!result.isSuccess() || result.getData() null) { return buildErrorResponse(response, HttpStatus.UNAUTHORIZED, result.getMsg()); } MapString, Object userInfo result.getData(); //
封装用户信息为JSON用户中心返回的信息已包含id/mobile/email等 String userInfoJson JSON.toJSONString(userInfo); //
写入请求头转发请求与方案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接口// 用户中心微服务的控制器 RestController RequestMapping(/member) public class MemberController { Resource private RedisTemplateString, Object redisTemplate; /** * 网关调用的Token校验接口 * param token 登录凭证 * return 用户信息 */ GetMapping(/verifyToken) public ResultMapString, Object verifyToken(RequestParam String token) { // 从Redis中获取用户信息登录时将token作为key用户信息作为value存入Redis String redisKey login:token: token; MapString, Object userInfo (MapString, Object) redisTemplate.opsForValue().get(redisKey); if (userInfo null) { return Result.fail(Token无效或已过期); } return Result.success(userInfo); } }
3 网关跨域配置兼容关键避免请求头丢失若网关已配置跨域CORS需显式允许自定义请求头X-User-Info否则浏览器会拦截该请求头导致下游微服务无法获取用户信息修改网关application.ymlspring: 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统一解析并封装为全局可访问的用户对象业务层可直接注入使用无需每次从请求头读取。
1 微服务通用依赖确保已引入!-- Web核心依赖含拦截器支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- fastjson2解析JSON格式的用户信息 -- dependency groupIdcom.alibaba.fastjson2/groupId artifactIdfastjson2/artifactId version
2.
32/version /dependency
2 步骤1定义用户信息实体类与网关传递的字段一致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 }
3 步骤2定义全局用户上下文ThreadLocal存储保证线程安全通过ThreadLocal存储当前请求的用户信息同一请求的所有线程业务层、服务层、DAO层均可直接获取线程安全且无侵入性。
package com.jagochan.train.common.context; import com.jagochan.train.common.entity.LoginUser; /** * 用户上下文ThreadLocal存储当前登录用户信息 */ public class UserContext { // ThreadLocal每个线程独立存储避免多线程数据混乱 private static final ThreadLocalLoginUser 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 步骤3实现拦截器解析请求头并设置用户上下文package com.jagochan.train.member.interceptor; import com.alibaba.fastjson
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 { //
从请求头中获取用户信息JSON String userInfoJson request.getHeader(USER_INFO_HEADER); if (userInfoJson ! null !userInfoJson.isEmpty()) { //
解析JSON为LoginUser对象 LoginUser loginUser JSON.parseObject(userInfoJson, LoginUser.class); //
设置到用户上下文ThreadLocal UserContext.setLoginUser(loginUser); } //
放行请求 return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //
请求结束后移除ThreadLocal中的数据防止内存泄漏 UserContext.remove(); } }
5 步骤4注册拦截器使其全局生效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(/**); } }
6 步骤5业务层直接使用用户信息无侵入极简微服务的控制器、服务层、DAO层均可直接通过UserContext获取当前登录用户信息无需任何参数传递示例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 ResultLoginUser getUserInfo() { //
快捷获取当前用户ID Long userId UserContext.getUserId(); //
获取完整用户信息 LoginUser loginUser UserContext.getLoginUser(); return Result.success(loginUser); } }
关键优化与避坑点
1 网关过滤器执行顺序必对过滤器getOrder()返回负数如-10保证在Gateway的路由过滤器、负载均衡过滤器之前执行先解析用户信息再转发请求若有多个全局过滤器按order值从小到大执行确保用户信息过滤器最先执行。
2 防止请求头中文/特殊字符乱码网关传递的用户信息为JSON字符串若包含中文如昵称需确保请求头编码为UTF-8Gateway默认支持微服务端无需额外配置HttpServletRequest默认按UTF-8解析请求头。
3 微服务ThreadLocal内存泄漏防护必须在afterCompletion中调用UserContext.remove()因为Tomcat使用线程池线程执行完后不会销毁若不移除ThreadLocal会持有对象引用导致内存泄漏afterCompletion会在请求处理完成包括异常后执行确保无论请求成功与否都会清理数据。
4 放行无需登录的接口网关层必做网关过滤器中必须放行登录接口、静态资源、Swagger、健康检查接口否则这些接口会被拦截返回401if (path.contains(/login) || path.contains(/swagger-ui) || path.contains(/v3/api-docs) || path.contains(/actuator/health)) { return chain.filter(exchange); }
5 生产环境JWT密钥安全JWT的密钥JWT_SECRET不可硬编码生产环境放入Nacos/Apollo配置中心通过Value注入密钥长度至少32位使用随机字符串生成避免被破解。
6 下游微服务无需再做登录校验所有请求必须经过网关网关层已统一校验Token有效性微服务端无需再校验Token直接使用UserContext的信息即可若微服务需对外提供接口绕过网关需单独添加Token校验逻辑。
拓展场景微服务之间调用传递用户信息若下游微服务之间相互调用如A微服务调用B微服务需要传递当前登录用户信息只需在Feign请求中添加请求头即可实现Feign请求拦截器package com.jagochan.train.common.feign; import com.jagochan.train.common.context.UserContext; import com.jagochan.train.common.entity.LoginUser; import com.alibaba.fastjson
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)); } } }所有微服务引入该公共拦截器后相互调用时会自动传递用户信息保证整个调用链路的用户信息一致性。
核心
总结核心方案网关全局过滤器解析Token→获取用户信息→写入自定义请求头下游微服务拦截器解析请求头→ThreadLocal封装→业务层直接使用性能优选使用JWT解析用户信息无需调用微服务网关层完成所有校验性能最高无侵入性微服务通过UserContext全局获取用户信息业务代码无需任何修改符合开闭原则链路一致性配合Feign拦截器实现微服务之间调用的用户信息自动传递全链路无感知统一异常网关层统一处理Token相关异常返回标准401响应下游微服务无需重复处理。
该方案适配Spring Cloud Gateway主流使用场景配置简单、扩展性强生产环境可直接落地完全解决网关向下游微服务传递登录用户信息的问题。