用户上下文透传机制详解

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 中,主要包括:

  1. 用户ID:user_id
  2. 用户名:username
  3. 用户标识:user_key
  4. 登录用户对象:login_user
  5. 角色权限: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);
        }

        // 配置客户端IP
        requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr());
    }
}

2.4 被调用服务的上下文处理

被调用的服务同样配置了 HeaderInterceptor拦截器,会从请求头中提取用户信息并存储到 SecurityContextHolder 中,从而完成整个调用链路上下文的传递。

3. 完整流程示例

以下是一个完整的用户上下文透传流程:

  1. 用户发起业务请求到网关
  2. 网关进行 JWT 验证,提取用户信息并添加到请求头
  3. 网关将请求转发到业务服务 A
  4. 业务服务 A 的 HeaderInterceptor 从请求头提取用户信息,存储到 SecurityContextHolder
  5. 业务服务 A 通过 Feign 调用业务服务 B
  6. FeignRequestInterceptorSecurityContextHolder中的用户信息添加到 Feign 请求头
  7. 业务服务 B 的 HeaderInterceptor从请求头提取用户信息,存储到 SecurityContextHolder
  8. 业务服务 B 处理请求,通过 SecurityContextHolder 获取用户信息
  9. 业务服务 B 返回结果给业务服务 A
  10. 业务服务 A 返回结果给网关
  11. 网关返回结果给用户

4. 技术优势

4.1 透明性

整个上下文传递过程对业务代码是透明的,开发者无需手动处理用户信息的传递。

4.2 异步支持

通过 TransmittableThreadLocal,即使在异步环境下也能正确传递用户上下文。

4.3 安全性

用户信息通过请求头传递,避免了在 URL 或请求体中暴露敏感信息。

4.4 可扩展性

通过配置不同的拦截器和过滤器,可以轻松扩展上下文传递的内容。

5. 配置要点

5.1 Feign 配置

FeignAutoConfiguration中注册了 Feign 拦截器:

@Configuration
public class FeignAutoConfiguration {
    @Bean
    public 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 拦截器的组合,实现了高效、安全、透明的用户上下文透传机制。这种设计不仅解决了微服务架构中用户身份传递的问题,还保证了在异步环境下上下文的正确传递,为构建安全可靠的微服务系统提供了坚实的基础。

posted @ 2025-10-26 00:10  WonderC  阅读(13)  评论(0)    收藏  举报