文档版本:1.0
状态:实现参考方案(落地前需结合现有 Gateway、认证模型和客户端版本进行技术评审)
客户端:Flutter(Android / iOS / macOS / Windows)、Web
服务端:Java / Spring Boot 微服务
基础设施:API Gateway、Redis、MySQL、密钥管理设施
目标:统一应用层接口加解密、会话生命周期、防重放、幂等重试、密钥管理、异常处理与安全验收。
1. 目标与设计原则
1.1 目标
- 保护受保护 API 请求与响应的机密性和完整性。
- 统一 Java、Flutter、Web 的协议格式,避免多端实现不一致。
- 将会话、密钥、nonce、防重放和协议版本纳入统一管理。
- 避免自动重试造成任务提交、审批、扣费等业务副作用重复执行。
- 适配 PMCS 微服务架构,在统一入口处理协议,同时保留业务服务自身的认证、授权和数据隔离责任。
- 提供可测试、可观测、可逐步升级的实现路径。
1.2 核心原则
- HTTPS 必须启用:应用层加密不能替代 TLS。握手、登录、刷新凭证和业务请求均不得通过明文 HTTP 传输。
- 使用认证加密:新协议使用 AES-256-GCM,不再将 AES-CBC 作为新协议默认算法。
- 密钥分离:会话加密密钥、KEK、登录凭证相互独立。
- 失败关闭:会话校验、密钥解包、认证标签校验或防重放检查失败时,不得降级为明文业务处理。
- 最小暴露面:只对明确标记的 API 使用应用层加密;健康检查、文件流、SSE 等特殊接口单独定义协议。
- 安全职责分层:Gateway 统一执行协议检查和加解密;业务服务仍必须执行认证、授权、租户隔离和业务状态校验。
- 业务幂等独立于防重放:nonce 解决同一密文请求重放,幂等键解决同一业务操作被重新发送。
- 协议版本化:协议字段、算法、错误码、密钥版本均有明确版本,不做静默降级。
- 日志不泄密:禁止记录会话密钥、KEK、完整认证凭证、明文敏感业务内容和完整密文载荷。
1.3 适用范围与边界
本方案是运行在 HTTPS 之上的应用层 API 加密协议。它不是 TLS 的替代品,也不能单独防止客户端被恶意软件控制、Web XSS、设备越狱或服务端被完全攻陷。
如果 PMCS 没有明确的合规要求或需要保护代理终止 TLS 后的请求内容,需评估应用层加密的成本与收益;无论是否启用应用层加密,都必须保留 TLS、身份认证、授权、输入校验和安全审计。
2. 总体架构
text
Flutter / Web
├─ 登录凭证管理
├─ SessionManager(状态机、代际、单飞握手)
├─ CryptoProvider(AES-256-GCM)
├─ RequestInterceptor(时间戳、nonce、requestId、幂等键)
└─ ResponseInterceptor(协议解析、认证解密、有限恢复)
│ HTTPS
▼
API Gateway(统一安全入口)
├─ TLS / 来源与请求大小检查 / 限流
├─ 识别协议版本与受保护路由
├─ 会话与认证状态检查
├─ nonce 原子消费、防重放
├─ AES-GCM AAD 校验与请求解密
├─ 将已验证的身份上下文传给下游
└─ 统一响应封装与响应加密
│ 受保护的内部网络 + 服务间认证
▼
PMCS 微服务
├─ user/auth:登录、认证、会话撤销
├─ task/project:业务授权、状态机、幂等
├─ file:文件元数据与上传下载策略
├─ im:消息服务集成
└─ ai:模型调用、额度和计费幂等
│
├─ Redis:会话、nonce、限流、幂等状态(按需求)
├─ MySQL:业务数据、必要的会话审计元数据
└─ KMS / Secret Manager:KEK 与密钥生命周期
2.1 Gateway 与 Spring MVC 的选择
如果使用 Spring Cloud Gateway(WebFlux),应在 Gateway 的 WebFlux 过滤器链中实现请求体读取/解密、响应体包装/加密,并处理响应式流与内存限制。Spring MVC 的 RequestBodyAdvice / ResponseBodyAdvice 不能直接视为 WebFlux Gateway 的实现方式。
若选择在 MVC 服务侧实现,则应封装统一安全组件并确保所有受保护 API 都经过该组件。禁止网关与下游对同一请求重复解密或重复加密。
2.2 内部服务调用
- 客户端会话密钥不用于服务间调用。
- 内部调用使用服务身份认证(例如受控网络内的 mTLS 或受管理的服务凭证)。
- Gateway 传递的用户上下文必须通过可信边界保护;客户端不得自行指定
X-User-Id、X-Tenant-Id等内部身份头。 - Gateway 应清除外部请求中所有保留的内部身份头,再由可信组件生成新的上下文。
- 每个业务服务仍须按用户、组织/租户、项目、任务等资源维度执行授权检查。
3. 协议与密码学规范
3.1 协议版本
新协议使用 protocolVersion = 2。服务端只接受明确启用的版本和算法。升级期间如需兼容旧客户端,必须建立单独的 v1 兼容策略、截止日期和流量监控;v1 不得通过失败回退方式自动启用。
3.2 算法参数
| 项目 | 规范 |
|---|---|
| 对称加密 | AES-256-GCM |
| 密钥长度 | 32 字节 |
| GCM IV / nonce | 12 字节,每个密钥下不得重复 |
| 认证标签 | 128 bit(16 字节) |
| 文本编码 | UTF-8 |
| 二进制编码 | 标准 Base64、无换行 |
| 请求随机数 | 独立于 GCM IV 的 X-PMCS-Nonce,使用密码学安全随机源 |
| 时间戳 | Unix epoch 毫秒 |
| TLS | HTTPS,生产环境强制 |
| 敏感密钥存储 | 会话密钥在 Redis 中以 KEK 加密后保存;KEK 存于 KMS/Secret Manager 或等效受控设施 |
重要区别: GCM 的 12 字节 IV 与 HTTP 防重放头中的 X-PMCS-Nonce 是两个不同字段,不能混用。GCM IV 用于密码学加密,HTTP nonce 用于防重放。
GCM IV 在同一密钥下不得重复。建议每次加密使用 CSPRNG 生成 12 字节随机 IV,并在每个会话密钥的加密量达到团队设定的保守阈值之前轮换密钥;高请求量场景应通过容量与风险评估制定密钥轮换策略。禁止将 GCM IV 固定为常量,禁止使用时间戳直接充当 IV。
3.3 请求信封
受保护的 JSON 请求使用以下 HTTP 头:
http
Authorization: Bearer <accessToken> # 需要登录的接口
X-PMCS-Session-Id: <sessionId>
X-PMCS-Protocol-Version: 2
X-PMCS-Key-Version: 1
X-PMCS-Timestamp: 1792134000000
X-PMCS-Nonce: <CSPRNG-generated-random-value>
X-PMCS-Request-Id: <requestId>
Idempotency-Key: <business-operation-id> # 有副作用的接口必须提供
Content-Type: application/json
请求体:
json
{
"protocolVersion": 2,
"keyVersion": 1,
"algorithm": "A256GCM",
"iv": "<Base64-12-byte-IV>",
"ciphertext": "<Base64(ciphertext || 16-byte-tag)>"
}
ciphertext 字段是加密数据与 GCM tag 的拼接结果。各端必须使用同一约定。不得在不同客户端中一部分把 tag 拼在末尾、一部分单独传输。
3.4 明文业务格式
客户端先将业务参数序列化为 UTF-8 JSON,再加密。例如:
json
{
"taskId": "task-1001",
"action": "submit",
"content": "任务成果内容"
}
序列化必须遵循统一约定:
- JSON UTF-8 编码。
- 明确空值、数字、布尔值、数组和对象的表示方式。
- 不依赖 JSON 字段顺序作为业务语义。
- 加密前限制明文大小;服务端解密后再次限制大小、JSON 深度和解析资源。
- 不允许通过重复字段、冲突字段或模糊类型绕过参数校验。
3.5 AAD(附加认证数据)
AES-GCM 必须使用 AAD 将密文绑定到请求上下文。推荐定义固定、无歧义的二进制或长度前缀编码格式,至少包含:
- 协议版本
- 密钥版本
- sessionId
- HTTP 方法(大写)
- 规范化路径(不含 fragment;查询参数是否纳入必须明确)
- 时间戳
- HTTP 防重放 nonce
- requestId
例如,协议可定义一个版本化的 AAD-v2 编码器,使用固定字段顺序、UTF-8 字节长度前缀进行拼接,避免简单用 | 分隔导致字段中包含分隔符而产生歧义。
客户端和服务端必须共享同一份 AAD 规范和测试向量。 服务端应从实际 HTTP 请求及已验证会话中构造预期 AAD,而不是盲目信任客户端提交的字段。需要绑定的用户/租户身份由服务端认证上下文确定。
3.6 响应协议
成功响应建议采用:
json
{
"protocolVersion": 2,
"requestId": "req-123",
"status": 0,
"code": "SUCCESS",
"data": {
"iv": "<Base64-12-byte-IV>",
"ciphertext": "<Base64(ciphertext || tag)>"
}
}
推荐将业务响应对象(业务 status、code、message、data 等)统一序列化后加密,并将外层固定协议元数据纳入响应 AAD,确保元数据与密文不能被任意拼接。外层仅保留协议版本、requestId、算法/密钥版本等最小必要字段。若为了兼容而将部分业务错误码留在明文外层,必须明确哪些字段可公开,并把它们绑定到认证数据中。
服务端错误发生在解密前时,可能无法安全地返回加密业务响应。此时仅返回固定、最小化、无敏感细节的协议错误信封,例如:
json
{
"protocolVersion": 2,
"requestId": "req-123",
"error": {
"code": "SESSION_EXPIRED",
"action": "RE_HANDSHAKE"
}
}
错误信封应使用统一的 HTTP 状态码映射和固定字段,不返回密钥、堆栈、内部主机名或敏感业务内容。不能通过不同的详细解密错误消息暴露密码学验证细节。
4. 握手与会话管理
4.1 握手接口
建议定义:
POST /api/v2/security/handshake:创建应用层会话。POST /api/v2/security/session/renew:在有效认证条件下更新或替换会话(如采用独立续期接口)。POST /api/v2/security/session/revoke:撤销当前会话。POST /api/v2/security/session/revoke-all:按已验证用户权限撤销全部会话(可选)。
这些路径是建议值,落地时需与现有 PMCS 路由规范统一。
握手请求示例:
json
{
"protocolVersion": 2,
"platform": "android",
"clientVersion": "1.0.0",
"deviceId": "client-generated-device-id"
}
规则:
- 握手本身仍然通过 HTTPS。
deviceId仅用于设备识别、限流和风险分析,不作为认证凭证。- 需要登录的业务会话必须验证 access token 或等价的有效登录凭证。
- 匿名会话只允许调用明确开放的匿名接口。
- 服务端通过 CSPRNG 生成不可预测的 sessionId 和 32 字节会话密钥。
- 服务端返回 sessionId、协议版本、keyVersion、过期时间和所需的最小元数据。
- 如果通过 TLS 返回会话密钥,应严格校验服务端证书;高风险客户端可进一步评估设备密钥绑定或公钥密钥协商。
- 握手限流至少结合 IP、deviceId、用户/账号(适用时)及全局保护。可信代理边界必须明确。
握手响应示例:
json
{
"protocolVersion": 2,
"sessionId": "<opaque-random-id>",
"keyVersion": 1,
"sessionKey": "<Base64-32-byte-key>",
"createdAt": 1792134000000,
"idleExpireAt": 1792135800000,
"absoluteExpireAt": 1792220400000
}
客户端应在收到响应后校验所有必需字段、Base64 解码结果和密钥长度;失败时不得建立半有效会话。
4.2 会话 Redis 设计
建议的逻辑结构:
| 字段 | 含义 |
|---|---|
sessionId |
随机会话 ID |
userId |
已认证用户 ID;匿名会话为空 |
tenantId |
组织/租户 ID(如适用) |
deviceId |
设备标识 |
authState |
ANONYMOUS / AUTHENTICATED / REVOKED |
keyVersion |
会话密钥版本 |
encryptedSessionKey |
使用 KEK 包裹后的会话密钥 |
createdAt |
创建时间 |
lastAccessAt |
最近有效访问时间 |
idleExpireAt |
空闲过期时间 |
absoluteExpireAt |
绝对过期时间 |
status |
ACTIVE / REVOKED / EXPIRED |
示例键名(具体命名可按项目规范调整):
text
PMCS:SEC:SESSION:{sessionId}
PMCS:SEC:DEVICE:{deviceId}
PMCS:SEC:NONCE:{sessionId}:{sha256(httpNonce)}
PMCS:SEC:RATE:IP:{ipHash}
PMCS:SEC:RATE:DEVICE:{deviceIdHash}
PMCS:SEC:RATE:USER:{userId}
PMCS:SEC:IDEMPOTENCY:{userId}:{route}:{idempotencyKey}
说明:
- Redis 中的会话密钥只能以 KEK 加密后的形式保存;禁止保留明文回退字段。
- KEK 解包失败、版本不支持或 KMS 不可用时,受保护请求应失败关闭。
- Redis TTL 与会话内部的绝对过期时间必须同时校验。
- 滑动续期不得超过
absoluteExpireAt。 - 会话撤销要有原子状态变更,并清理或失效相关索引。
- Redis ACL、网络隔离、TLS(如部署支持)和最小权限必须配置。
- 会话审计仅记录必要元数据,不保存可直接恢复的密钥。
4.3 会话状态机
text
NEW
│ handshake succeeds
▼
ACTIVE ── idle/absolute timeout ──► EXPIRED
│ revoke / account disabled / security incident
▼
REVOKED
EXPIRED / REVOKED
│ fresh authenticated handshake
▼
NEW → ACTIVE
退出登录、账号封禁、密码重置、刷新凭证撤销等行为是否撤销当前会话或全部会话,应形成明确的产品与安全策略。
4.4 密钥轮换
keyVersion用于标识 KEK 版本或协议定义的密钥版本,字段语义必须明确,不能混用 KEK 版本与会话密钥版本。- 建议分别定义
sessionKeyVersion、kekVersion与protocolVersion,避免概念混淆。 - 新建会话使用当前有效密钥配置。
- KEK 轮换时,采用受控的重新包裹流程:旧 KEK 在迁移窗口内只用于解包,重新包裹后写入新版本,再验证迁移结果。
- 旧密钥只有在确认没有依赖后才能退役。
- 任一解包失败都应明确报错并告警,不允许静默降级到明文密钥或其他未声明版本。
5. 防重放与幂等控制
5.1 请求时间戳与 nonce
每个受保护请求应携带:
X-PMCS-Timestamp:Unix epoch 毫秒。X-PMCS-Nonce:CSPRNG 生成的随机值,协议规定固定编码与长度范围。X-PMCS-Request-Id:用于日志追踪,不替代 nonce。X-PMCS-Session-Id:当前会话 ID。
服务端流程:
- 校验时间戳为合法整数,并检查与可信服务端时间的偏差。
- 检查会话和密钥版本。
- 对 nonce 做 SHA-256 摘要并以 Redis 原子
SET key value NX EX ttl消费。 - 若 key 已存在,拒绝请求并记录安全事件。
- 校验 AAD 并完成 GCM 认证解密。
- 进入业务授权与幂等流程。
nonce TTL 必须覆盖整个时间戳有效窗口。若允许时钟偏差 ±300 秒,初始配置可以采用 660 秒并经测试确认;所有节点应使用可信时间同步,不能因客户端时间错误而无界放宽窗口。
注意:上述顺序会导致 nonce 消费后即使认证解密失败,该 nonce 也不能重用。这是安全优先的行为。客户端重试时必须生成新的 HTTP nonce 和新的 GCM IV;对业务副作用仍需通过幂等键避免重复执行。
5.2 幂等键
以下接口应要求幂等键或等效的业务唯一操作 ID:
- 创建任务、发布任务、提交成果。
- 审批通过/驳回、状态变更。
- AI 生成任务、AI 计费与额度扣减。
- 其他会产生不可逆或有成本副作用的操作。
幂等记录至少包含:
- 已验证的用户/租户身份。
- 规范化业务路由或操作类型。
Idempotency-Key。- 业务请求摘要(对规范化明文业务请求计算 SHA-256;避免把敏感原文直接写入记录)。
- 状态:
IN_PROGRESS/SUCCEEDED/FAILED_RETRYABLE/FAILED_FINAL。 - 结果引用或可安全重建的结果。
- 创建时间、过期时间。
相同幂等键但请求摘要不同,必须拒绝;相同键、相同请求且已成功,应返回原业务结果或其安全引用。并发重复请求应通过唯一约束、原子操作或事务保证只有一个执行者。
幂等状态与业务写入应尽可能处于同一数据库事务或通过可靠状态机协调。对于外部 AI 供应商调用,需考虑超时后的未知结果、供应商请求 ID、计费对账与补偿流程,不能简单将超时视为失败后重新扣费。
6. Gateway / 服务端处理流程
6.1 请求处理顺序
text
接收 HTTPS 请求
↓
检查路由是否要求应用层加密
↓
检查 Content-Type、原始 body 大小、请求头重复与协议版本
↓
读取会话 ID,校验会话状态和过期时间
↓
校验时间戳与 HTTP nonce,原子消费 nonce
↓
解析 envelope,校验 algorithm / protocolVersion / keyVersion
↓
取得会话密钥并校验 AAD、执行 AES-GCM 认证解密
↓
解析 JSON,校验明文 body 大小、深度、字段类型
↓
建立可信用户/租户上下文,执行认证与授权
↓
调用下游业务服务(服务间认证)
↓
取得业务结果并构造统一响应
↓
响应 AES-GCM 加密(响应使用独立新 IV)
↓
返回协议响应
具体实现可根据安全与可用性需求微调,但不得在认证解密失败后继续执行业务,不得在密钥获取失败后回退明文。
6.2 伪代码
text
handleProtectedRequest(request):
enforceHttps()
enforceRoutePolicy(request.route)
enforceHeaderUniquenessAndSizeLimits(request)
session = sessionStore.findAndValidate(request.sessionId)
validateAuthenticatedIdentityIfRequired(session, request)
validateTimestamp(request.timestamp)
consumeNonceAtomically(session.id, sha256(request.httpNonce), nonceTtl)
envelope = parseEnvelopeWithStrictLimits(request.body)
assert envelope.protocolVersion == supportedVersion
assert envelope.algorithm == "A256GCM"
assert envelope.keyVersion == session.keyVersion
key = keyManager.unwrapSessionKey(session.encryptedSessionKey)
aad = aadCodec.fromTrustedRequestContext(request, session)
plaintext = aesGcmDecryptAndVerify(key, envelope.iv, envelope.ciphertext, aad)
body = strictJsonParse(plaintext)
enforceDecryptedBodyLimits(body)
context = trustedIdentityContext(session)
result = invokeAuthorizedBusinessHandler(context, body, request.idempotencyKey)
responsePlaintext = serializeCanonicalResponse(result)
responseEnvelope = aesGcmEncryptWithFreshIv(key, responsePlaintext, responseAad(...))
return protocolResponse(responseEnvelope)
该伪代码用于表达职责与顺序,不是可直接复制运行的实现代码。实际系统还需要明确异常映射、流式接口、响应式取消、幂等事务边界和审计策略。
6.3 受保护路由与豁免路由
应维护显式路由策略,而不是只靠字符串包含判断或宽泛白名单。
路由策略至少包括:
- 是否要求 HTTPS。
- 是否要求登录。
- 是否要求应用层加密。
- 是否要求幂等键。
- 请求与响应大小上限。
- 是否支持文件流、SSE 或其他特殊媒体类型。
- 是否允许匿名访问。
- 限流策略。
握手、登录、短信验证码等接口可以不使用应用层加密,但必须通过 HTTPS、认证/风控、限流和严格参数校验。不要把"明文白名单"理解为"无需安全保护"。
7. 错误码与客户端恢复策略
7.1 建议的错误码
| 错误码 | 含义 | 客户端行为 |
|---|---|---|
SESSION_NOT_FOUND |
会话不存在 | 允许有限次数重握手 |
SESSION_EXPIRED |
会话已过期 | 重握手;重新校验登录状态 |
SESSION_REVOKED |
会话已撤销 | 清理本地会话;按认证状态引导登录 |
AUTH_REQUIRED |
缺少有效认证 | 按登录流程处理,不自动无限重试 |
AUTH_FORBIDDEN |
无权访问资源 | 不重握手、不重试 |
PROTOCOL_VERSION_UNSUPPORTED |
协议版本不支持 | 显示可恢复错误或要求升级客户端 |
KEY_VERSION_UNSUPPORTED |
密钥版本不支持 | 受控升级或重新建立会话;服务端告警 |
CRYPTO_AUTH_FAILED |
GCM 标签/AAD 校验失败 | 不自动循环重握手;记录 requestId 并有限恢复 |
REQUEST_REPLAYED |
nonce 已使用 | 拒绝该请求;重试时生成新 nonce,并检查幂等状态 |
TIMESTAMP_INVALID |
时间戳不合法或超窗 | 不无限重试;提示检查设备时间或重试策略 |
PAYLOAD_TOO_LARGE |
请求或解密后 body 超限 | 不重试,提示缩小请求 |
IDEMPOTENCY_CONFLICT |
幂等键对应不同请求 | 拒绝并提示冲突 |
RATE_LIMITED |
触发限流 | 遵循 Retry-After(若提供)和退避策略 |
SECURITY_SERVICE_UNAVAILABLE |
会话/密钥/防重放设施不可用 | 失败关闭,显示可恢复错误 |
错误响应应同时使用适当 HTTP 状态码;业务错误码不能完全取代 HTTP 状态码。错误信息要避免泄露内部细节。
7.2 客户端会话状态机
text
UNINITIALIZED
↓ handshake
ACTIVE
├─ 明确会话过期 → STALE → SINGLE-FLIGHT HANDSHAKE → ACTIVE
├─ 会话撤销 → REVOKED → 清理会话并重新认证
├─ 认证/授权失败 → 保持会话,按业务处理
├─ 网络失败 → 按网络策略重试,不自动判定会话失效
└─ 密文认证失败 → 记录 requestId,有限恢复或终止并报告
客户端要求:
- 会话密钥默认只保存在内存;如业务确实需要跨进程持久化,应采用平台安全存储并评估风险。
- Web 不应把会话密钥和长期认证凭证随意保存在 localStorage。优先使用内存中的短期会话密钥,并实施 CSP、严格输出编码、依赖审计和 XSS 防护。浏览器无法彻底防止同源恶意脚本在运行期间调用加密能力。
- 单飞握手只合并同一会话恢复过程,不应导致不同用户账号或不同设备的状态混用。
- 自动重试必须有最大次数、退避和随机抖动。
- 每次重新加密生成新 GCM IV、新 HTTP nonce 和新的 requestId;业务重试保留同一个
Idempotency-Key。 - 对于可能已经执行但响应丢失的操作,先查询幂等结果或业务状态,不盲目重复执行。
8. 请求与响应大小、文件和特殊接口
8.1 大小限制
至少在以下层次设置限制:
- 网关接收到的原始 HTTP body 字节数。
- Base64 解码后的密文大小。
- 解密后的明文字节数。
- JSON 解析深度、数组长度和字段长度。
- 响应体大小与加密缓冲区大小。
限制应在读取完整请求体之前尽可能生效,避免超大 body 占用内存。对流式内容不要在内存中一次性缓存全部数据。
8.2 文件服务
PMCS 的图片、视频、文档和附件建议使用文件服务或对象存储的受控上传下载流程:
- 业务 API 负责鉴权、生成短时有效的上传/下载授权。
- 对象存储使用私有 Bucket、最小权限和短期签名 URL。
- 文件元数据、所属任务、上传人、访问权限由 PMCS 服务端控制。
- 上传后校验文件大小、类型、扩展名与实际内容,必要时做病毒扫描或安全检测。
- 不要把大型二进制文件直接 Base64 后塞进 JSON 加密信封。
- 如有端到端加密或对象内容额外加密要求,应另行定义文件加密协议、密钥托管、预览和分享策略。
8.3 SSE、WebSocket、IM 与流式 AI
HTTP JSON 加解密协议不应直接套用于 SSE、WebSocket、环信 IM 或流式 AI 响应。应分别定义:
- 建连认证方式与凭证生命周期。
- 消息级认证与重放策略。
- 心跳与断线重连规则。
- 消息大小限制。
- 流式输出的错误处理和取消行为。
WebSocket/SSE 连接认证不能只依赖最初握手成功;需要考虑连接有效期、令牌过期、撤销和重新认证。
9. 日志、审计、监控与故障处理
9.1 日志规范
允许记录:
- requestId、traceId、服务名、路由模板、HTTP 状态码。
- 协议版本、密钥版本标识(非密钥本身)。
- 会话 ID 的不可逆摘要或内部关联 ID。
- 安全事件类型、限流结果、会话撤销原因。
- 耗时、重试次数、依赖服务错误类别。
禁止记录:
- 会话密钥、KEK、明文登录凭证、完整 token。
- 完整请求密文或敏感明文业务内容。
- 可直接恢复的密码、验证码或隐私数据。
- 未经脱敏的完整附件 URL、签名参数和其他可复用访问凭证。
9.2 建议监控指标
- 握手成功率、失败率、按原因分类的计数。
- 请求解密成功率、GCM 验证失败数。
- nonce 重复拒绝数、时间戳错误数。
- Redis 延迟、超时、错误率和连接池使用情况。
- 会话创建数、过期数、撤销数。
- 幂等冲突、处理中超时、重复请求命中率。
- 客户端重握手次数、恢复成功率、恢复失败率。
- 加解密耗时、请求体大小分布、网关内存使用情况。
安全事件指标应设置合理阈值和告警,避免因单个客户端时钟错误造成告警风暴。
9.3 故障策略
| 故障 | 建议行为 |
|---|---|
| Redis 不可用 | 对依赖会话与防重放的受保护请求失败关闭;不得跳过检查 |
| KEK/KMS 不可用 | 不解包、不接受无法验证的会话;返回可恢复的服务错误并告警 |
| nonce 存储超时 | 无法确认是否已消费时,拒绝本次请求或按幂等查询恢复,不得假定 nonce 未使用 |
| 下游业务服务超时 | 根据业务幂等状态判断是否可安全恢复 |
| 加密响应失败 | 不返回未受保护的业务明文作为兜底;记录 requestId 并处理失败 |
| 客户端协议不兼容 | 返回明确版本错误,不自动切换到较弱算法 |
| 节点时钟偏差 | 监控并告警,使用可靠时间同步;不通过无限放宽时间窗口解决 |
10. 测试与验收标准
10.1 密码学与跨端测试
- Java、Flutter、Web 对相同测试向量能够互相解密。
- 修改密文、GCM tag、AAD、sessionId、路径或时间戳后认证失败。
- 同一会话密钥下不会复用 GCM IV;异常输入不会导致进程崩溃或泄漏敏感数据。
- Base64 格式、UTF-8、空值、最大长度、非法 JSON 的行为一致。
- 不支持的算法和协议版本明确拒绝。
10.2 会话与防重放测试
- 未认证用户不能获得已认证会话权限。
- 过期、撤销和绝对超时的会话不能继续调用业务接口。
- 相同 HTTP nonce 并发提交时只有一个请求通过防重放检查。
- nonce 过期时间覆盖时间戳有效窗口。
- Redis、KMS 故障时不会绕过安全检查。
- 多设备、并发握手、会话撤销和密钥轮换不存在状态竞争问题。
10.3 业务幂等测试
- 相同幂等键、相同业务请求不会重复创建任务、重复审批或重复扣费。
- 相同幂等键、不同请求摘要会被拒绝。
- 并发重复请求只有一个业务执行者。
- 业务成功但响应丢失时,可以返回原结果或查询到最终状态。
- 外部 AI 供应商超时、回调延迟和计费对账不会造成重复扣费。
10.4 性能与安全测试
- 在目标并发量下进行加解密、Redis 和 Gateway 压测。
- 测试大 body、恶意 JSON、异常请求头、重复头和高频握手。
- 验证日志中不存在密钥、token 和敏感明文。
- 测试密钥轮换、协议升级、滚动部署、Redis 故障和服务重启。
- 对应用层加密入口执行渗透测试与代码审计。
10.5 上线门槛
以下任意一项未通过,不应启用生产强制加密流量:
- 跨端测试向量不一致。
- KEK 解包失败仍可回退明文密钥。
- GCM 认证失败后仍执行业务。
- 防重放依赖不可用时继续放行。
- 状态变更或扣费接口没有可验证的幂等策略。
- 内部身份头可被外部请求伪造。
- 生产日志包含可复用密钥或认证凭证。
11. 推荐的代码模块与职责
| 模块 / 类 | 职责 |
|---|---|
pmcs-common-crypto |
AES-GCM、Base64、AAD 编解码、加解密测试向量 |
pmcs-common-security |
安全上下文、协议异常、路由策略接口 |
pmcs-auth |
握手、会话创建/撤销、登录状态关联 |
SessionStore |
Redis 会话读取、续期、撤销和原子状态转换 |
KeyManager |
KEK 版本管理、会话密钥包裹与解包 |
NonceStore |
nonce 摘要与原子 SET NX EX |
IdempotencyService |
幂等键、请求摘要、状态与结果恢复 |
PmcsSecurityGatewayFilter |
Gateway 统一协议检查、请求解密、响应加密 |
ProtocolErrorHandler |
统一错误信封和 HTTP 状态码映射 |
SecurityAuditService |
安全事件审计与脱敏日志 |
SessionManager(客户端) |
会话状态机、单飞握手、过期与恢复 |
CryptoProvider(客户端) |
AES-GCM、AAD、跨端协议一致性 |
RequestInterceptor(客户端) |
注入时间戳、nonce、requestId、幂等键 |
ResponseInterceptor(客户端) |
响应认证解密、错误分类、有限恢复 |
公共组件应通过接口隔离 Redis、KMS 和具体 Gateway 实现,便于测试和替换。加解密工具不得直接依赖业务 Controller 或业务数据库。
12. 实施阶段建议
阶段一:协议冻结
- 确定 Gateway 技术栈(WebFlux 或 MVC)。
- 确定登录凭证、匿名接口、认证会话和组织/租户上下文的关系。
- 冻结协议版本、信封字段、AAD 编码、错误码、大小限制和密钥版本语义。
- 为 Java、Flutter、Web 建立共享测试向量与自动化测试。
阶段二:服务端安全基础设施
- 实现 AES-256-GCM 与严格的 AAD 校验。
- 实现会话存储、KEK 包裹、会话撤销和双重过期校验。
- 实现 nonce 原子消费、分层限流和统一异常处理。
- 实现 Gateway 请求/响应安全过滤器。
- 实现安全日志、指标与故障关闭行为。
阶段三:业务接入
- 先接入登录后最简单的查询接口。
- 再接入任务创建、提交、审批等状态变更接口,并完成幂等。
- 接入 AI 任务与额度/计费流程,验证未知执行结果恢复。
- 为文件上传下载、SSE、WebSocket、IM 建立独立协议边界。
阶段四:客户端与灰度
- 实现 Flutter、Web 的统一协议客户端。
- 验证单飞握手、会话代际和有限重试。
- 如需兼容旧客户端,单独部署 v1 兼容策略并设置退出期限。
- 灰度启用,监控解密失败、会话恢复、延迟和错误分布。
- 完成安全验收后逐步扩大流量。
13. 落地前必须确认的项目参数
以下内容不能只凭通用方案推定,实施前应由 PMCS 项目组确认:
| 待确认项 | 为什么重要 |
|---|---|
| 当前 Gateway 是 Spring Cloud Gateway / WebFlux 还是 MVC 网关 | 决定过滤器和请求体处理实现方式 |
| 登录 token 的签发、刷新、撤销机制 | 决定握手认证与会话绑定 |
| 是否需要兼容已经发布的旧客户端 | 决定是否需要 v1/v2 双协议迁移 |
| Web 端的威胁模型与存储策略 | 决定 XSS 防护、会话密钥存储和认证 Cookie 设计 |
| 任务、审批、AI 计费的业务幂等边界 | 决定幂等记录和事务协调方式 |
| 目标并发量、请求大小和响应延迟目标 | 决定限流、缓存、缓冲区和性能测试指标 |
| KMS / Secret Manager 的可用设施 | 决定 KEK 的存储、访问权限和轮换方式 |
| 文件、SSE、WebSocket 和 IM 的具体接口形态 | 决定哪些接口使用 JSON 信封、哪些采用专用协议 |
14. 参考资料
以下资料用于实现前的安全设计与代码评审:
- OWASP Cryptographic Storage Cheat Sheet
cheatsheetseries.owasp.org/cheatsheets... - OWASP REST Security Cheat Sheet
cheatsheetseries.owasp.org/cheatsheets... - NIST SP 800-38D:Galois/Counter Mode (GCM)
csrc.nist.gov/pubs/sp/800... - OWASP Session Management Cheat Sheet
cheatsheetseries.owasp.org/cheatsheets...
15. 最终决策摘要
PMCS 新版接口安全协议建议采用:
- HTTPS + AES-256-GCM。
- 每个会话独立随机密钥,Redis 仅保存 KEK 保护后的会话密钥。
- 版本化请求/响应信封及明确的 AAD 规范。
- 会话与用户认证状态绑定,支持撤销、空闲过期和绝对过期。
- 时间戳 + Redis 原子 nonce 防重放。
- 状态变更和计费接口使用独立的业务幂等机制。
- Gateway 统一协议处理,业务服务仍独立执行授权和数据隔离。
- 密钥、Redis 或认证校验失败时失败关闭,禁止静默降级。
- Flutter、Web、Java 共享测试向量,并在灰度和安全验收后上线。
实施提醒: 本文是完整的设计与实现参考基线,不等同于已经通过安全审计的成品协议。正式上线前应完成协议冻结、密码学库选择、跨端测试、威胁建模、异常注入、性能测试和安全评审。