Java中验签功能通过自定义@RequireSign注解、参数标准化拼接、HandlerInterceptor统一拦截校验实现,支持HMAC/SHA256或RSA算法,密钥动态加载且防时序攻击。

Java 中实现接口请求的验签功能,核心是通过封装统一的签名验证逻辑,避免在每个 Controller 方法里重复写验签代码。通常结合 Spring 的拦截器(HandlerInterceptor)或 AOP 实现,同时配合自定义注解、工具类和密钥管理,让验签过程可配置、可复用、易维护。
定义验签注解与配置规则
先创建一个自定义注解,用于标记需要验签的接口:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireSign {
boolean required() default true; // 是否强制验签
String signType() default "HMAC-SHA256"; // 签名算法类型
}
这样可以在 Controller 方法上直接使用:@RequireSign(signType = "HMAC-SHA256")
便于按需开启/关闭,也支持不同算法切换。
提取请求参数并生成待签名字符串
验签前需标准化原始参数,关键点包括:
- 只取
GET的 query 参数 或POST的application/x-www-form-urlencoded/application/json请求体(JSON 需解析后转为 key-value) - 过滤掉签名字段本身(如
sign、signature)、空值、系统保留字段(如timestamp可保留但需参与排序) - 对所有 key 进行字典序升序排列,拼接成
key1=value1&key2=value2格式(注意 URL 编码) - 末尾追加约定好的密钥(
appSecret),再进行哈希运算
示例工具方法:
立即学习“Java免费学习笔记(深入)”;
public static String buildSignString(Map<String, String> params, String secret) {
return params.entrySet().stream()
.filter(e -> !e.getKey().equals("sign") && StringUtils.isNotBlank(e.getValue()))
.sorted(Map.Entry.comparingByKey())
.map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&")) + "&key=" + secret;
}
拦截器中统一执行验签逻辑
编写 HandlerInterceptor,在 preHandle 中完成签名校验:
- 解析请求方式,提取全部有效参数(含 body 解析,建议用
ContentCachingRequestWrapper避免流被读取多次) - 反射获取目标方法上的
@RequireSign注解,判断是否需要验签 - 从参数中取出客户端传入的
sign值、timestamp(做时间戳校验,如 5 分钟内有效) - 调用工具类生成服务端签名,并与客户端签名比对(推荐用
MessageDigest.isEqual()防止时序攻击) - 失败则返回
401 Unauthorized或自定义错误码,不放行请求
注册拦截器时可设置路径匹配规则,例如只对 /api/** 生效。
密钥与算法的安全管理
不要硬编码密钥,应通过以下方式管理:
- 应用启动时从配置中心(Nacos/Apollo)或加密配置文件加载
appSecret,按appId动态查密钥 - 签名算法支持插件化,如
HmacUtils.hmacSha256(secret.getBytes(), data.getBytes()) - 敏感字段(如密钥、私钥)禁止打日志;验签失败时只记录 traceId 和基础信息,不打印原始参数
- 可扩展支持 RSA 验签:客户端用私钥签名,服务端用公钥验签,适合更高安全要求场景
不复杂但容易忽略。


















