API接口签名验证方案设计
David Ng | 2026-07-26T02:41:00 | Java, Security
设计一个防篡改防重放的API签名验证方案,包括timestamp+nonce+sign的完整实现
# API接口签名验证方案设计 开放API给第三方调用,光靠Token还不够,还需要签名验证防止篡改和重放攻击。 ## 签名流程 1. 客户端把所有参数按字母排序拼接 2. 加上timestamp、nonce和appSecret 3. 计算HMAC-SHA256得到sign 4. 服务端用同样的方式验签 ```java // 客户端签名 public class SignUtil { public static String sign(Map params, String appSecret, String nonce, String timestamp) { // 1. 参数按key字母排序 TreeMap sorted = new TreeMap(params); StringBuilder sb = new StringBuilder(); for (Map.Entry entry : sorted.entrySet()) { sb.append(entry.getKey()).append("=") .append(entry.getValue()).append("&"); } // 2. 拼接时间戳和随机数 sb.append("timestamp=").append(timestamp); sb.append("&nonce=").append(nonce); // 3. HMAC-SHA256签名 return hmacSha256(sb.toString(), appSecret); } } ``` ## 服务端验签 ```java @Component public class SignFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletRequest request = (HttpServletRequest) req; String timestamp = request.getHeader("X-Timestamp"); String nonce = request.getHeader("X-Nonce"); String sign = request.getHeader("X-Sign"); // 1. 验证时间戳:超过5分钟的请求拒绝(防重放) long diff = System.currentTimeMillis() - Long.parseLong(timestamp); if (diff > 5 * 60 * 1000) { throw new RuntimeException("请求已过期"); } // 2. 验证nonce:Redis判断是否用过(防重放) if (!redisTemplate.opsForValue() .setIfAbsent("nonce:" + nonce, "1", 5, TimeUnit.MINUTES)) { throw new RuntimeException("重复请求"); } // 3. 验证签名 String expected = SignUtil.sign(getParams(request), appSecret, nonce, timestamp); if (!expected.equals(sign)) { throw new RuntimeException("签名验证失败"); } chain.doFilter(req, res); } } ``` **三道防线**:timestamp防过期重放、nonce防短时重放、sign防篡改。