4. 整体架构设计:分层架构概览、租户上下文传递、签名服务网关设计

多租户签名链路的核心挑战在于:如何在共享基础设施上,为每个租户提供隔离、安全、可审计的签名服务。本章从分层架构、上下文传递、网关设计三个维度,构建一个可落地、可扩展的多租户签名传输体系。

4.1 分层架构概览

整体架构采用四层模型,自顶向下依次为:接入层、网关层、服务层、存储层。每层职责清晰,层间通过标准化接口通信,确保租户隔离与水平扩展能力。

架构设计原则:
  • 租户隔离优先:每一层都必须感知租户身份,拒绝跨租户数据访问。
  • 无状态水平扩展:网关层与服务层保持无状态,租户上下文通过请求头传递。
  • 可审计可追溯:所有签名请求、密钥操作、数据变更均记录租户ID与操作链路。
多租户签名链路分层架构 接入层 API网关 / 负载均衡 / TLS终结 / 租户身份识别 签名服务网关层 租户路由 / 签名策略分发 / 限流熔断 / 审计日志 签名服务层 密钥管理 / 签名引擎 / 验签服务 / 租户密钥隔离 存储层 租户密钥库 / 签名记录库 / 配置中心 / 数据分片 租户隔离边界 租户A 租户B 租户C ...
架构要点:接入层完成租户身份识别后,后续所有层不再重复解析租户身份,而是通过统一的租户上下文(TenantContext)传递。这种设计避免了每层重复认证,同时降低了跨层耦合。

4.2 租户上下文传递

租户上下文是贯穿整个签名链路的核心数据载体。它必须包含以下关键字段:

字段 类型 说明 传递方式
tenantId String 租户唯一标识 HTTP Header / RPC Attachment
appId String 租户下应用标识 HTTP Header
requestId String 全局请求追踪ID HTTP Header / 日志上下文
signPolicy String 签名策略标识(如RSA/SM2) 从配置中心获取
authToken String 租户认证令牌(脱敏传递) HTTP Header(仅接入层可见)

上下文传递采用ThreadLocal + 请求拦截器模式,确保在异步调用场景下不丢失租户信息:

// 租户上下文持有者
public class TenantContextHolder {
    private static final ThreadLocal<TenantContext> CONTEXT = new InheritableThreadLocal<>();

    public static void set(TenantContext ctx) {
        CONTEXT.set(ctx);
    }

    public static TenantContext get() {
        return CONTEXT.get();
    }

    public static void clear() {
        CONTEXT.remove();
    }
}

// 网关过滤器:提取并设置租户上下文
public class TenantContextFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String tenantId = exchange.getRequest().getHeaders()
                .getFirst("X-Tenant-Id");
        String appId = exchange.getRequest().getHeaders()
                .getFirst("X-App-Id");
        TenantContext ctx = new TenantContext(tenantId, appId);
        TenantContextHolder.set(ctx);
        return chain.filter(exchange)
                .then(Mono.fromRunnable(TenantContextHolder::clear));
    }
}
关键注意事项:
  • 异步回调、线程池任务必须显式传递TenantContext,否则会导致租户信息丢失。
  • 禁止在日志、异常堆栈中明文输出authToken,防止租户凭证泄露。
  • 租户上下文生命周期严格限定在一次请求范围内,请求结束后必须清理ThreadLocal。

4.3 签名服务网关设计

签名服务网关是多租户签名链路的流量入口与策略执行点。它承担以下核心职责:

  • 租户路由:根据tenantId将请求路由到对应的签名服务实例或集群。
  • 签名策略分发:从配置中心拉取租户签名策略(算法、密钥版本、有效期),注入到请求上下文中。
  • 限流与熔断:按租户维度实施流量控制,防止单个租户突发流量影响全局。
  • 审计日志:记录每次签名请求的租户、时间、算法、结果,支持事后审计。
签名服务网关内部处理流程 签名请求入口 租户身份识别与认证 签名策略加载与校验 租户级限流熔断 审计日志记录 转发至签名服务

网关层采用插件化架构,每个处理环节(认证、策略、限流、审计)均可独立配置与扩展。以下是一个典型的网关路由配置示例:

# 多租户签名网关路由配置
spring:
  cloud:
    gateway:
      routes:
        - id: sign-service-route
          uri: lb://sign-service
          predicates:
            - Path=/api/v1/sign/**
          filters:
            - name: TenantAuthentication   # 租户认证过滤器
            - name: SignPolicyLoader       # 签名策略加载过滤器
            - name: TenantRateLimiter      # 租户限流过滤器
              args:
                key-resolver: "#{@tenantKeyResolver}"
                redis-rate-limiter:
                  replenishRate: 100       # 每秒令牌数
                  burstCapacity: 200       # 突发容量
            - name: AuditLogging           # 审计日志过滤器
网关设计最佳实践:
  • 租户维度限流:使用Redis + Lua脚本实现原子性限流,避免分布式计数器不一致。
  • 策略缓存:签名策略配置缓存到本地Caffeine,设置合理TTL(如60秒),减少配置中心压力。
  • 优雅降级:当签名服务不可用时,网关应返回明确的租户级错误码,而非直接抛出500。
  • 灰度发布:支持按租户ID哈希将流量路由到不同版本的签名服务,实现租户级灰度。