在 Spring Cloud 微服务架构中,我们通常使用 API 网关(如 Spring Cloud Gateway)作为流量入口,负责统一的用户身份校验和请求路由。本文将介绍一种通用的用户信息传递方案:网关完成认证后通过请求头传递用户信息,服务端使用拦截器结合 ThreadLocal
实现用户上下文的自动获取与清理。
📚 目录
-
架构概览与设计思想
-
Spring Cloud Gateway 设置用户信息头
-
UserContext:用户上下文工具类
-
UserInfoInterceptor:拦截器实现
-
MvcConfig:注册拦截器(按需加载)
-
Spring Boot 自动装配支持
-
线程复用风险与清理机制
-
统一用户信息访问方式
-
总结与建议
架构概览与设计思想
[浏览器 / APP]
↓
[SpringCloud Gateway] ------ 登录校验 + 添加用户ID至请求头(user-info)
↓
[业务服务] ------ 使用拦截器提取 user-info,存入 ThreadLocal
↓
[Service / Mapper 层] ------ 通过 UserContext.getUser() 获取用户ID
设计目标:
-
登录状态统一在网关校验,后端服务无需再次解析 Token
-
后端服务无侵入获取当前用户信息
-
请求结束后自动清除用户信息,避免线程复用污染
Spring Cloud Gateway 设置用户信息头
在登录校验通过后,我们可以使用 Gateway 的 GlobalFilter
将解析后的用户ID写入请求头中:
exchange.getRequest().mutate()
.header("user-info", userId.toString())
.build();
也可以用 exchange.getRequest().mutate().headers(...)
添加多个自定义信息,例如用户名、角色等。
UserContext:用户上下文工具类
public class UserContext {
private static final ThreadLocal<Long> tl = new ThreadLocal<>();
public static void setUser(Long userId) {
tl.set(userId);
}
public static Long getUser() {
return tl.get();
}
public static void removeUser(){
tl.remove();
}
}
UserInfoInterceptor:拦截器实现
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String userInfo = request.getHeader("user-info");
if (StrUtil.isNotBlank(userInfo)) {
UserContext.setUser(Long.valueOf(userInfo));
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
UserContext.removeUser(); // 一定要清理,防止线程复用导致数据串用!
}
}
MvcConfig:注册拦截器(按需加载)
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor());
}
}
🔍 为什么使用 @ConditionalOnClass(DispatcherServlet.class)?
该注解的作用是:仅当项目中存在 DispatcherServlet(即为 Spring Web 应用)时才加载该配置类。
这样做可以:
-
增强公共模块的通用性(非 Web 模块不加载 MVC 配置)
-
避免在非 Web 项目中报类找不到错误
Spring Boot 自动装配支持
在 common 模块中配置自动注册支持:
📄 resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hmall.common.config.MvcConfig
线程复用风险与清理机制
为什么一定要调用 ThreadLocal.remove()
?
Spring Boot 默认使用 Tomcat,Tomcat 内部维护一个线程池,线程复用带来两大隐患:
-
数据串用: 用户 A 请求设置了 userId=1,未清除,线程复用处理用户 B 请求时会错误复用 A 的 userId。
-
内存泄漏: ThreadLocalMap key 是弱引用,value 是强引用,不调用 remove 会导致 value 无法 GC 回收。
✅ 正确做法是在 afterCompletion
中移除:
UserContext.removeUser();
统一用户信息访问方式
在任何业务代码中:
Long userId = UserContext.getUser();
无需解析 Token、无需手动传参,统一、简洁、解耦。
总结与建议
-
✅ 网关做登录校验 + 用户信息封装,是最佳实践
-
✅ 后端使用拦截器 + ThreadLocal 自动注入用户上下文
-
✅
@ConditionalOnClass
保证配置在 Web 项目中生效,避免不必要的类加载 -
✅
afterCompletion
中清除ThreadLocal
,防止线程复用问题和内存泄漏
📝 推荐统一网关行为,例如:在请求头中添加 user-id、role、username 等,避免后端服务关注鉴权细节,聚焦业务逻辑。
📌 本文适用于 Spring Cloud Gateway + Spring Boot 微服务体系,实战性强,建议在项目中统一实践。