Spring Boot 接口签名:这5个坑让你的 API 裸奔

接口没签名 → 参数随便改、请求随便重放、金额随便篡改。5 分钟看完即用,每个坑附错误写法 + 正确姿势。


1. 签名不含时间戳,重放随便来

// ❌ 只验 sign,不验 timestamp → 拿旧请求重放,照样过
String sign = request.getHeader("X-Sign");
String params = body + "&key=" + SECRET;  // 没有时间戳
if (!DigestUtils.md5Hex(params).equals(sign)) {
    throw new BizException("签名错误");
}
// ✅ 签名串必须含 timestamp + nonce,过期拒收
String ts = request.getHeader("X-Timestamp");
String nonce = request.getHeader("X-Nonce");
if (Math.abs(System.currentTimeMillis() - Long.parseLong(ts))
        > 5 * 60 * 1000) {
    throw new BizException("请求已过期");
}
String sign = DigestUtils.md5Hex(
    body + "&ts=" + ts + "&nonce=" + nonce + "&key=" + SECRET);

铁律:签名必须绑定时间窗口(建议 5 分钟),不然抓包重放百发百中。


2. 签名算法只用 MD5,彩虹表秒破

// ❌ 纯 MD5(参数+key) → 太弱,加个盐就破了
String sign = DigestUtils.md5Hex(params + SECRET);
// ✅ HMAC-SHA256 + 随机 nonce,破解成本指数级
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(SECRET.getBytes(), "HmacSHA256"));
String sign = Base64.getEncoder()
    .encodeToString(mac.doFinal(params.getBytes()));

生产至少用 HMAC-SHA256,别再裸 MD5。


3. Body 流在 Filter 里读完,Controller 拿不到

// ❌ Filter 里 request.getReader() 读完 → 流已消费,Controller 里 request body 为空
String body = IOUtils.toString(request.getReader());
// ✅ 包装成 ContentCachingRequestWrapper,流可重复读
public class SignFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain) {
        ContentCachingRequestWrapper wrapper =
            new ContentCachingRequestWrapper(request);
        chain.doFilter(wrapper, response);
        byte[] body = wrapper.getContentAsByteArray();  // 随手拿
        verifySign(new String(body, StandardCharsets.UTF_8));
    }
}

4. 签名 key 硬编码,泄露就全完

// ❌ 代码里写死 → 一次泄露,所有环境沦陷
private static final String SECRET = "prod-secret-key-2024";
// ✅ yml 配置 + 配置中心加密,不同环境不同 key
@Value("${api.sign.secret}")
private String secret;

// application.yml
api:
  sign:
    secret: ${API_SIGN_SECRET}   // 环境变量注入,不进 Git

5. GET/POST 签名串规则不统一

// ❌ GET 按 query string 拼,POST 按 body 拼 → 两套逻辑,必出 bug
if ("GET".equals(method)) {
    signStr = sortAndJoin(request.getParameterMap());
} else {
    signStr = IOUtils.toString(request.getReader());
}
// ✅ 统一规则:字典序拼接所有参数 + timestamp + nonce + key
TreeMap sorted = new TreeMap<>();
sorted.put("timestamp", ts);
sorted.put("nonce", nonce);
sorted.putAll(extractParams(request));  // GET/POST 统一提取
String signStr = sorted.entrySet().stream()
    .map(e -> e.getKey() + "=" + e.getValue())
    .collect(Collectors.joining("&")) + "&key=" + SECRET;

避坑速查表

#

坑点

快速修复

1

没 timestamp/nonce

签名串绑定时间戳,5 分钟过期

2

只用 MD5

换 HMAC-SHA256

3

Filter 消费 Body


ContentCachingRequestWrapper

4

key 硬编码

环境变量/配置中心,不进 Git

5

GET/POST 规则不一致

统一按字典序拼接参数


一句话总结:接口签名就四条铁律——加时间戳防重放、HMAC 替代 MD5、Body 用 Wrapper 重复读、key 不进代码。把这 5 个坑都封上,你的 API 才算穿上铠甲。

展开阅读全文

更新时间:2026-06-03

标签:科技   接口   时间   随便   参数   规则   绑定   字典   环境变量   错误   代码

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight All Rights Reserved.
Powered By 71396.com 闽ICP备11008920号
闽公网安备35020302034903号

Top