RuoYi-Cloud 用户上下文透传机制详解
引言
在微服务架构中,用户上下文的传递是一个关键问题。当一个请求在多个微服务之间调用时,如何确保用户身份信息能够正确传递到每个服务中,是实现安全认证和权限控制的基础。RuoYi-Cloud 通过 TransmittableThreadLocal 和 Feign 拦截器的组合,实现了高效的用户上下文透传。
1. 核心组件介绍
1.1 TransmittableThreadLocal
RuoYi-Cloud 使用阿里巴巴开源的 TransmittableThreadLocal 来存储用户上下文信息。相比于标准的 ThreadLocal,TransmittableThreadLocal 能够在父子线程之间传递数据,解决了异步场景下的上下文传递问题。
在 SecurityContextHolder 中,定义了基于 TransmittableThreadLocal 的线程上下文:
private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>();
1.2 Feign 拦截器
Feign 是 Spring Cloud 中常用的声明式 HTTP 客户端,用于服务间的调用。为了在 Feign 调用中传递用户上下文,RuoYi-Cloud 实现了自定义的 FeignRequestInterceptor。
2. 用户上下文透传实现机制
2.1 上下文存储
用户上下文信息存储在 SecurityContextHolder 中,主要包括:
- 用户ID:
user_id - 用户名:
username - 用户标识:
user_key - 登录用户对象:
login_user - 角色权限:
role_permission
这些信息通过 TransmittableThreadLocal存储,确保在线程间正确传递。
2.2 网关到业务服务的上下文传递
当请求从网关转发到业务服务时,网关已经在请求头中添加了用户信息:
// 设置用户信息到请求
addHeader(mutate, SecurityConstants.USER_KEY, userkey);
addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
业务服务通过 HeaderInterceptor 拦截器从请求头中提取用户信息并存储到 SecurityContextHolder 中:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID));SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));// ...return true;
}
2.3 Feign 调用中的上下文传递
当业务服务需要通过 Feign 调用其他服务时,FeignRequestInterceptor会自动将当前线程中的用户上下文信息添加到 Feign 请求的头部:
@Override
public void apply(RequestTemplate requestTemplate) {HttpServletRequest httpServletRequest = ServletUtils.getRequest();if (StringUtils.isNotNull(httpServletRequest)) {Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);// 传递用户信息请求头,防止丢失String userId = headers.get(SecurityConstants.DETAILS_USER_ID);if (StringUtils.isNotEmpty(userId)) {requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId);}String userKey = headers.get(SecurityConstants.USER_KEY);if (StringUtils.isNotEmpty(userKey)) {requestTemplate.header(SecurityConstants.USER_KEY, userKey);}String userName = headers.get(SecurityConstants.DETAILS_USERNAME);if (StringUtils.isNotEmpty(userName)) {requestTemplate.header(SecurityConstants.DETAILS_USERNAME, userName);}String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);if (StringUtils.isNotEmpty(authentication)) {requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);}// 配置客户端IPrequestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr());}
}
2.4 被调用服务的上下文处理
被调用的服务同样配置了 HeaderInterceptor拦截器,会从请求头中提取用户信息并存储到 SecurityContextHolder 中,从而完成整个调用链路上下文的传递。
3. 完整流程示例
以下是一个完整的用户上下文透传流程:
- 用户发起业务请求到网关
- 网关进行 JWT 验证,提取用户信息并添加到请求头
- 网关将请求转发到业务服务 A
- 业务服务 A 的
HeaderInterceptor从请求头提取用户信息,存储到SecurityContextHolder - 业务服务 A 通过 Feign 调用业务服务 B
FeignRequestInterceptor将SecurityContextHolder中的用户信息添加到 Feign 请求头- 业务服务 B 的
HeaderInterceptor从请求头提取用户信息,存储到SecurityContextHolder - 业务服务 B 处理请求,通过
SecurityContextHolder获取用户信息 - 业务服务 B 返回结果给业务服务 A
- 业务服务 A 返回结果给网关
- 网关返回结果给用户
4. 技术优势
4.1 透明性
整个上下文传递过程对业务代码是透明的,开发者无需手动处理用户信息的传递。
4.2 异步支持
通过 TransmittableThreadLocal,即使在异步环境下也能正确传递用户上下文。
4.3 安全性
用户信息通过请求头传递,避免了在 URL 或请求体中暴露敏感信息。
4.4 可扩展性
通过配置不同的拦截器和过滤器,可以轻松扩展上下文传递的内容。
5. 配置要点
5.1 Feign 配置
在 FeignAutoConfiguration中注册了 Feign 拦截器:
@Configuration
public class FeignAutoConfiguration {@Beanpublic RequestInterceptor requestInterceptor() {return new FeignRequestInterceptor();}
}
5.2 拦截器配置
在 WebMvcConfig中配置了 HeaderInterceptor
@Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getHeaderInterceptor()).addPathPatterns("/**").excludePathPatterns(excludeUrls).order(-10);
}
结论
RuoYi-Cloud 通过 TransmittableThreadLocal 和 Feign 拦截器的组合,实现了高效、安全、透明的用户上下文透传机制。这种设计不仅解决了微服务架构中用户身份传递的问题,还保证了在异步环境下上下文的正确传递,为构建安全可靠的微服务系统提供了坚实的基础。