
1. 为什么需要AK/SK签名机制想象一下这样的场景你开发了一个提供天气查询服务的API突然发现有人用脚本疯狂调用你的接口每分钟请求上万次不仅消耗服务器资源还可能被恶意利用。这时候AK/SK签名机制就像给API加了一把防盗锁——只有拥有合法钥匙AK/SK且能正确签名的人才能进门。AKAccess Key ID和SKSecret Access Key是一对密钥组合AK相当于用户名公开暴露在请求中SK则是绝密密码只存在于客户端和服务端的安全存储中实际案例中某电商平台曾因API未做签名验证被黑客伪造订单请求半小时内损失数百万。而采用AK/SK签名后即使请求被拦截攻击者也无法伪造有效签名。2. 签名机制核心设计原理2.1 防重放三要素时间戳是签名机制的第一道防线。我在项目中遇到过这样的坑最初没加时间戳验证结果抓包工具重放旧请求就能反复调用接口。现在我们的标准做法是// 服务端验证时间戳示例 if (Math.abs(System.currentTimeMillis() - requestTimestamp) 300000) { throw new ApiException(请求已过期); }**随机数Nonce**防重放更彻底。建议用UUIDRedis实现String nonce UUID.randomUUID().toString(); if (redisTemplate.opsForValue().setIfAbsent(nonce:nonce, 1, 5, TimeUnit.MINUTES)) { // 通过验证 } else { throw new ApiException(重复请求); }参数签名最关键。有次我忘记对参数排序导致客户端和服务端签名不一致。现在都用TreeMap自动排序MapString, String params new TreeMap(); // 自动按键排序 params.put(page, 1); params.put(limit, 20);2.2 签名算法选型对比算法类型安全性性能适用场景MD5低高内部低安全要求接口SHA-256中中普通业务接口HMAC-SHA256高较低支付/金融类接口实测数据显示HMAC-SHA256虽然比MD5慢约30%但能有效防止彩虹表攻击。建议关键业务至少使用SHA-256。3. 完整Java实现方案3.1 客户端签名生成先看工具类封装public class SignUtil { private static final String ALGORITHM HmacSHA256; public static String generateSign(String appId, String appSecret, long timestamp, String nonce, MapString, String params) throws Exception { // 1. 参数排序 MapString, String sortedParams new TreeMap(params); // 2. 拼接签名字符串 StringBuilder sb new StringBuilder(); sb.append(appId).append(appId) .append(appSecret).append(appSecret) .append(nonce).append(nonce) .append(×tamp).append(timestamp); for (Map.EntryString, String entry : sortedParams.entrySet()) { sb.append().append(entry.getKey()).append().append(entry.getValue()); } // 3. HMAC加密 Mac mac Mac.getInstance(ALGORITHM); mac.init(new SecretKeySpec(appSecret.getBytes(), ALGORITHM)); byte[] signBytes mac.doFinal(sb.toString().getBytes()); // 4. 转16进制 return Hex.encodeHexString(signBytes); } }实际调用示例MapString, String params new HashMap(); params.put(userId, 1001); params.put(orderNo, 20230815001); String sign SignUtil.generateSign( your_app_id, your_app_secret, System.currentTimeMillis(), UUID.randomUUID().toString(), params );3.2 服务端验证逻辑验证流程要处理各种异常情况public class ApiAuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 1. 基础校验 String appId request.getHeader(appId); String timestamp request.getHeader(timestamp); String nonce request.getHeader(nonce); String sign request.getHeader(sign); if (StringUtils.isAnyBlank(appId, timestamp, nonce, sign)) { throw new ApiException(400, 缺少认证参数); } // 2. 时间有效性验证 if (Math.abs(System.currentTimeMillis() - Long.parseLong(timestamp)) 300000) { throw new ApiException(401, 请求已过期); } // 3. 获取对应SK String appSecret getSecretByAppId(appId); // 从数据库或缓存获取 // 4. 参数提取与签名验证 MapString, String params new HashMap(); EnumerationString paramNames request.getParameterNames(); while (paramNames.hasMoreElements()) { String name paramNames.nextElement(); params.put(name, request.getParameter(name)); } String serverSign SignUtil.generateSign(appId, appSecret, Long.parseLong(timestamp), nonce, params); if (!MessageDigest.isEqual(serverSign.getBytes(), sign.getBytes())) { throw new ApiException(403, 签名验证失败); } return true; } }4. 高级安全增强策略4.1 密钥轮换方案固定密钥风险大我们采用三级密钥体系主密钥存储在KMS中每月轮换临时密钥由主密钥生成有效期1天会话密钥单次请求有效// 密钥生成示例 public class KeyManager { public static String generateTempKey(String masterKey) { String timeFactor LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); return DigestUtils.md5Hex(masterKey timeFactor); } }4.2 请求限流保护结合签名做精细化限流RestController RateLimiter(value 100, key #header.appId) // 每个AK每秒100次 public class ApiController { PostMapping(/api) public Response api(RequestHeader AuthHeader header, RequestBody MapString, Object params) { // 业务处理 } }5. 实战中的坑与解决方案坑1参数编码不一致现象iOS和Android签名结果不同解决统一使用URLEncoder.encode(param, UTF-8)坑2Body内容签名遗漏现象POST请求body修改后验证仍通过解决将body的MD5值加入签名参数String bodyMd5 DigestUtils.md5Hex(request.getInputStream()); params.put(_body, bodyMd5);坑3时间同步问题现象某些设备因时区设置导致签名失败解决服务端返回当前时间戳客户端计算差值校准在金融级API项目中我们还增加了双向证书认证国密SM4加密的增强方案。不过对于大多数业务场景完善的AK/SK机制已经能防御90%的API安全威胁。