4. 整体架构设计:分层架构概览、租户上下文传递、签名服务网关设计
多租户签名链路的核心挑战在于:如何在共享基础设施上,为每个租户提供隔离、安全、可审计的签名服务。本章从分层架构、上下文传递、网关设计三个维度,构建一个可落地、可扩展的多租户签名传输体系。
4.1 分层架构概览
整体架构采用四层模型,自顶向下依次为:接入层、网关层、服务层、存储层。每层职责清晰,层间通过标准化接口通信,确保租户隔离与水平扩展能力。
架构设计原则:
- 租户隔离优先:每一层都必须感知租户身份,拒绝跨租户数据访问。
- 无状态水平扩展:网关层与服务层保持无状态,租户上下文通过请求头传递。
- 可审计可追溯:所有签名请求、密钥操作、数据变更均记录租户ID与操作链路。
架构要点:接入层完成租户身份识别后,后续所有层不再重复解析租户身份,而是通过统一的租户上下文(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哈希将流量路由到不同版本的签名服务,实现租户级灰度。