请求安全(加密 / 签名 / 防重放)
Coco Web 在请求进入业务代码之前,提供三道可独立开关的请求安全过滤器:AES-GCM 请求解密、HMAC-SHA256 签名校验、防重放。三者都绑定 coco.web 命名空间,依赖前一章介绍的请求体缓存来重复读取原始请求体。
它们协作的典型顺序是:请求体缓存 → 解密 → 验签 → 防重放 → 业务逻辑。
AES-GCM 请求解密
功能简介
请求解密过滤器使用 AES/GCM/NoPadding 对请求体密文执行认证解密,由 AesGcmCocoRequestDecryptor 实现。GCM 是带认证的加密模式:解密时会一并校验认证标签,认证失败即视为解密失败并拒绝请求,因此密文一旦被篡改无法通过。
客户端通过请求头声明加密材料:加密标记(默认 X-Coco-Encrypted)、应用标识(X-Coco-App-Id)、密钥标识(X-Coco-Key-Id)、IV(X-Coco-IV)、算法(X-Coco-Algorithm)。框架用 appId 或 appId:keyId 从本地密钥映射中查找 AES 密钥,后者优先级更高。密钥、IV、密文默认按 Base64 解码。
如何启用与配置
由 coco.web.encryption 控制,默认启用(但需配置密钥后才会真正生效)。
coco:
web:
encryption:
enabled: true
required: false
default-algorithm: AES-GCM
gcm-tag-length-bits: 128
key-encoding: BASE64
iv-encoding: BASE64
payload-encoding: BASE64
keys:
my-app: "Base64EncodedAesKey=="
my-app:key-2024: "AnotherBase64Key=="
关键配置项
| 配置项 | 默认值 | 说明 |
|---|---|---|
coco.web.encryption.enabled | true | 是否启用 AES 解密设施 |
coco.web.encryption.required | false | 是否要求所有请求都必须加密 |
coco.web.encryption.default-algorithm | AES-GCM | 请求未声明算法时的默认解密算法 |
coco.web.encryption.gcm-tag-length-bits | 128 | GCM 认证标签长度(bit),小于等于零时恢复默认 |
coco.web.encryption.key-encoding | BASE64 | 密钥文本编码 |
coco.web.encryption.iv-encoding | BASE64 | IV 文本编码 |
coco.web.encryption.payload-encoding | BASE64 | 密文请求体文本编码 |
coco.web.encryption.keys | 空 | 本地 AES 密钥映射,键为 appId 或 appId:keyId |
coco.web.encryption.encrypted-header-name | X-Coco-Encrypted | 加密标记请求头 |
coco.web.encryption.app-id-header-name | X-Coco-App-Id | 应用标识请求头 |
coco.web.encryption.key-id-header-name | X-Coco-Key-Id | 密钥标识请求头 |
coco.web.encryption.iv-header-name | X-Coco-IV | IV 请求头 |
coco.web.encryption.algorithm-header-name | X-Coco-Algorithm | 算法请求头 |
HMAC-SHA256 签名校验
功能简介
签名校验过滤器用共享密钥对规范化后的请求文本计算 HMAC-SHA256,由 HmacSha256CocoSignatureVerifier 实现。它同时支持十六进制和 Base64 两种签名文本格式,并使用 MessageDigest.isEqual 做常量时间比较,避免时序侧信道泄露。
签名材料同样从请求头读取:应用标识(X-Coco-App-Id)、密钥标识(X-Coco-Key-Id)、时间戳(X-Coco-Timestamp)、随机串(X-Coco-Nonce)、签名(X-Coco-Sign,兜底 X-Coco-Signature)、算法(X-Coco-Sign-Algorithm)。密钥同样从 appId 或 appId:keyId 本地映射查找。
时间戳与时钟偏差
签名请求默认要求携带时间戳,并校验时间戳是否落在允许的时钟偏差窗口内(默认 300 秒)。这既能限制签名的有效期,也为防重放提供时间边界。客户端与服务端时钟差异超出 max-clock-skew-seconds 的请求会被拒绝,因此部署时应保证双方时钟同步(如 NTP)。
如何启用与配置
由 coco.web.signature 控制,默认启用(需配置密钥后生效)。
coco:
web:
signature:
enabled: true
required: false
timestamp-required: true
timestamp-validation-enabled: true
max-clock-skew-seconds: 300
default-algorithm: HMAC-SHA256
secrets:
my-app: "shared-secret-value"
my-app:key-2024: "another-secret"
关键配置项
| 配置项 | 默认值 | 说明 |
|---|---|---|
coco.web.signature.enabled | true | 是否启用签名校验设施 |
coco.web.signature.required | false | 是否要求所有请求必须携带签名 |
coco.web.signature.timestamp-required | true | 签名请求是否必须携带时间戳 |
coco.web.signature.timestamp-validation-enabled | true | 是否校验签名时间戳窗口 |
coco.web.signature.max-clock-skew-seconds | 300 | 允许的客户端与服务端时间差(秒),小于等于零时恢复默认 |
coco.web.signature.default-algorithm | HMAC-SHA256 | 默认签名算法 |
coco.web.signature.secrets | 空 | 本地签名密钥映射,键为 appId 或 appId:keyId |
coco.web.signature.signature-header-name | X-Coco-Sign | 签名请求头 |
coco.web.signature.signature-fallback-header-name | X-Coco-Signature | 签名兜底请求头 |
coco.web.signature.timestamp-header-name | X-Coco-Timestamp | 时间戳请求头 |
coco.web.signature.nonce-header-name | X-Coco-Nonce | 随机串请求头 |
防重放
功能简介
防重放过滤器基于请求携带的 appId、keyId、时间戳、随机串(可选叠加 HTTP 方法和请求路径)拼装防重放键,在重放窗口内每个键只允许占用一次,从而拦截被截获后原样重放的请求。它默认保护已签名和已加密的请求。
占用状态由 CocoReplayStore 保存,框架内置两种实现:
- 内存存储
InMemoryCocoReplayStore(默认,store-type: in-memory):进程内ConcurrentHashMap,带后台定时清理过期键,适合单进程应用和本地开发。 - JDBC 存储
JdbcCocoReplayStore(store-type: jdbc):使用业务项目提供的JdbcOperations和预建表,通过防重放键摘要(SHA-256)的唯一约束实现跨实例原子占用。框架不会创建表、迁移结构或管理数据源,表结构需业务预建(键replay_key_hash、过期时间expires_at_epoch_millis)。
(此外还提供基于 Spring Data Redis 的 redis 存储类型。)
集群部署边界
内存存储是进程本地的,多个实例之间互不感知,无法在集群中真正防重放。InMemoryCocoReplayStore 启动时会打印告警提示这一点。集群部署必须把 store-type 切换为 jdbc(或 redis),或自行提供共享的 CocoReplayStore 实现。
如何启用与配置
由 coco.web.replay 控制,默认启用。
coco:
web:
replay:
enabled: true
required: false
store-type: in-memory # in-memory / jdbc / redis
protect-signed-requests: true
protect-encrypted-requests: true
include-method: true
include-path: true
ttl-seconds: 300
cleanup-interval-seconds: 60
max-clock-skew-seconds: 300
jdbc:
table-name: coco_replay_key
redis:
key-prefix: "coco:replay:"
关键配置项
| 配置项 | 默认值 | 说明 |
|---|---|---|
coco.web.replay.enabled | true | 是否启用防重放设施 |
coco.web.replay.required | false | 是否要求所有请求都通过防重放校验 |
coco.web.replay.store-type | in-memory | 存储类型:in-memory / jdbc / redis |
coco.web.replay.protect-signed-requests | true | 是否保护已签名请求 |
coco.web.replay.protect-encrypted-requests | true | 是否保护已加密请求 |
coco.web.replay.include-method | true | 防重放键是否包含 HTTP 方法 |
coco.web.replay.include-path | true | 防重放键是否包含请求路径 |
coco.web.replay.ttl-seconds | 300 | 重放窗口秒数,小于等于零时恢复默认 |
coco.web.replay.cleanup-interval-seconds | 60 | 内存/JDBC 存储过期键清理间隔秒数 |
coco.web.replay.max-clock-skew-seconds | 300 | 允许的客户端时间戳最大时钟偏差秒数,小于零时恢复默认 |
coco.web.replay.jdbc.table-name | coco_replay_key | JDBC 存储的业务预建表名(校验为合法 SQL 标识符) |
coco.web.replay.redis.key-prefix | coco:replay: | Redis 存储键前缀 |
共同的重要边界:multipart 绕过
三道过滤器都依赖请求体缓存重复读取原始请求体,而请求体缓存默认排除 multipart/(文件上传)和 application/octet-stream。这意味着文件上传请求会绕过 AES 解密、签名校验和防重放过滤器。
设计原因见「Web 运行时」章:文件上传体积大、不适合整体读入内存。若业务需要对上传接口做加密、验签或防重放保护,应采用独立于请求体缓存的方案(例如对元数据而非文件体签名,或在网关侧处理)。