应创建SignatureVerificationException继承RuntimeException,提供含消息、原因、签名值、数据摘要等参数的构造方法,并按失败场景设置错误码,避免使用通用异常。

在 Java 中为签名验签失败设计自定义异常,核心是**明确失败语义、区分错误场景、保持可捕获性和可调试性**。不建议直接抛出通用异常(如 RuntimeException 或 IllegalArgumentException),而应创建专用于签名验证上下文的异常类。
定义签名验签专用异常类
继承 RuntimeException(适用于编程错误或流程中不可恢复的校验失败)或 Exception(若需强制调用方处理,如某些安全敏感场景要求显式兜底)。推荐前者,因验签失败通常是逻辑/数据问题,而非可预期的业务分支。
- 命名清晰,如
SignatureVerificationException - 提供多个构造方法:含消息、原因(
Throwable)、签名原始值、待验数据摘要等,便于排查 - 可选添加错误码(如
INVALID_SIGNATURE、EXPIRED_TIMESTAMP、UNKNOWN_KEY_ID)提升可观测性
常见验签失败场景与对应异常使用
不要把所有失败都塞进一个异常;可在同一异常类中通过不同构造参数或子类体现差异:
-
签名格式非法:Base64 解码失败、长度不符合算法要求 → 构造时传入
"Invalid signature encoding"+ 原始字符串 - 密钥不匹配或不存在:公钥查不到、密钥 ID 无效 → 消息含 keyId,异常中记录该字段
-
签名验证不通过:JCA 的
Signature#verify()返回false→ 不要仅抛异常,建议先 log 原始数据摘要、时间戳、算法名,再抛异常 -
时间戳过期或未来时间:虽属业务规则,但常与验签强耦合 → 可复用该异常,消息中说明
"Timestamp expired: 2024-01-01T10:00:00Z"
配合使用示例(精简版)
在验签方法中主动识别失败点,并封装上下文信息:
立即学习“Java免费学习笔记(深入)”;
public void verify(String payload, String signatureB64, String keyId) {
byte[] sigBytes = Base64.getDecoder().decode(signatureB64);
PublicKey pubKey = keyStore.getPublicKey(keyId);
if (pubKey == null) {
throw new SignatureVerificationException(
"Public key not found",
keyId,
"KEY_NOT_FOUND"
);
}
<pre class="brush:php;toolbar:false;">Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubKey);
sig.update(payload.getBytes(StandardCharsets.UTF_8));
if (!sig.verify(sigBytes)) {
String digest = DigestUtils.sha256Hex(payload); // 日志用
throw new SignatureVerificationException(
"Signature verification failed",
payload, digest, keyId,
"SIGNATURE_MISMATCH"
);
}
}
日志与监控建议
自定义异常本身不负责打日志,但应在 catch 处统一记录关键字段:
- 记录异常类型、错误码、keyId、payload 摘要(避免明文)、时间戳(如有)
- 对高频失败(如固定 keyId 报错)可加监控告警
- 生产环境避免在异常消息中泄露敏感信息(如私钥路径、完整 payload)


















