高 困难
接口安全设计#
一句话答案#
接口安全设计五层防护:HTTPS 防窃听 → Token 认证鉴权 → 签名防篡改 → 时间戳+Nonce 防重放 → 限流防刷;对外开放 API 还需 AppKey/AppSecret + 请求签名机制。
核心要点
安全威胁与对应方案#
| 威胁 | 攻击方式 | 防御 |
|---|---|---|
| 窃听 | 抓包获取明文数据 | HTTPS (TLS) |
| 篡改 | 修改请求参数 | 签名校验 (HMAC-SHA256) |
| 重放 | 截获请求重复发送 | 时间戳 + Nonce + 签名 |
| 伪造 | 冒充合法客户端 | AppKey + Token 认证 |
| 刷接口 | 高频恶意请求 | 限流 + 验证码 + 黑名单 |
| 越权 | 访问他人数据 | 数据权限校验 |
请求签名机制#
签名生成步骤:
1. 拼接签名串: method + url + timestamp + nonce + sorted_params
2. 计算签名: sign = HMAC-SHA256(签名串, appSecret)
3. 请求附带: appKey + timestamp + nonce + sign
服务端验证:
1. 检查 timestamp 是否在 5 分钟内(防重放)
2. 用 appKey 查 appSecret
3. 用相同算法计算签名,常量时间比对是否一致(防篡改)
4. 验签通过后再检查 nonce 是否已使用(Redis SETNX,TTL 5min)——先验签,避免未签名的垃圾请求往 Redis 写 nonceplaintext// 客户端生成签名
String signStr = "POST" + "/api/order" + timestamp + nonce + sortedParams;
String sign = HmacUtils.hmacSha256Hex(appSecret, signStr);
// 服务端验签
@Component
public class SignVerifyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, ...) {
String timestamp = req.getHeader("X-Timestamp");
String nonce = req.getHeader("X-Nonce");
String sign = req.getHeader("X-Sign");
String appKey = req.getHeader("X-AppKey");
// 1. 时间戳校验(5分钟窗口)
if (Math.abs(System.currentTimeMillis() - Long.parseLong(timestamp)) > 300000) {
throw new SecurityException("Request expired");
}
// 2. 验签(用常量时间比较,防计时攻击)
String secret = appKeyService.getSecret(appKey);
String expectedSign = computeSign(req, timestamp, nonce, secret);
if (!MessageDigest.isEqual(sign.getBytes(UTF_8), expectedSign.getBytes(UTF_8))) {
throw new SecurityException("Invalid signature");
}
// 3. Nonce 防重放(验签通过后再写 Redis)
if (!Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent("nonce:" + appKey + ":" + nonce, "1", 5, MINUTES))) {
throw new SecurityException("Duplicate request");
}
return true;
}
}java防重放三件套#
时间戳 (timestamp): 请求生成时间,服务端拒绝超过 5 分钟的请求
Nonce (一次性随机数): Redis 存已用 nonce,TTL=5min,重复拒绝
签名 (sign): 时间戳+nonce 参与签名计算,不能被篡改
三者缺一不可:
- 只有时间戳: 5分钟内可重放
- 只有nonce: 需要永久存储所有nonce(不可行)
- 时间戳+nonce: 只需存5分钟内的nonce + 签名保证不能改时间戳plaintext对外 API 安全设计#
开放平台(如支付宝/微信)标准模式:
1. 分配 AppKey (标识) + AppSecret (密钥)
2. 请求必须签名,参数按字典序排列后签名:可用 HMAC-SHA256(对称,双方共享 AppSecret);支付类平台多用非对称签名——支付宝 RSA2(SHA256withRSA),微信支付 APIv3 用 SHA256-RSA2048(放 Authorization 头),不再用 v2 的 MD5/HMAC-SHA256
3. 敏感字段加密传输(如用平台公钥 RSA 加密;回调报文用 AES-256-GCM 加密)
4. 回调通知也签名(双向验签)
5. IP 白名单(可选)
6. 频率限制(每秒/每天调用上限)plaintext内部接口安全#
服务间调用安全:
1. mTLS(双向 TLS):服务间证书互验
2. JWT 服务令牌:服务注册时获取,调用时携带
3. 网络隔离:内部服务不暴露公网
4. 网关统一鉴权:所有请求经 Gateway 校验后转发plaintext面试回答(2分钟版)
接口安全从外到内分五层。第一层 HTTPS 防窃听,所有通信 TLS 加密。第二层认证鉴权,JWT Token 或 AppKey 验证调用方身份。第三层签名防篡改:把请求方法、URL、参数、时间戳、Nonce 拼接后做 HMAC-SHA256 签名,服务端用同样算法验签,参数被改签名就对不上。第四层防重放:时间戳限制 5 分钟窗口 + Nonce 一次性随机数存 Redis 去重 + 两者都参与签名不能被篡改。第五层限流防刷,配合验证码和 IP 黑名单。对外开放 API 的标准模式是分配 AppKey+AppSecret,请求参数按字典序排列后签名,敏感数据 RSA 加密。内部服务间通信用 mTLS 双向认证或 JWT 服务令牌。
追问与易错
追问方向:
- “签名算法为什么用 HMAC 不用 MD5?”→ 纯 MD5 没有密钥,谁都能算;自己拼
MD5(secret+参数)这种「哈希+密钥」有长度扩展攻击风险,MD5 本身也已不抗碰撞;HMAC 是专为「带密钥的消息认证」设计的结构,配 SHA-256 更安全 - “时间戳 5 分钟窗口会不会太长?”→ 需要容忍客户端时钟偏差,太短正常请求会被拒绝
- “Nonce 用 Redis 存会不会 OOM?”→ 设 TTL=5min(和时间戳窗口一致),自动过期不会积累
- “HTTPS 了还需要签名吗?”→ 需要。HTTPS 只保护传输链路、只验证服务端身份,不证明「调用方是谁」,也不防重放;签名证明请求来自持有 AppSecret 的一方且参数未被改(包括 TLS 在网关终止后的内部链路)。签名防不了持有密钥的客户端自己改参数——所以 AppSecret 只能放在服务端,App/前端里的密钥可被逆向,只算提高门槛
易错点:
- ❌ “有 HTTPS 就够安全了”——HTTPS 不认证调用方、不防重放,用户自装证书抓包也能改请求
- ❌ “只校验时间戳就能防重放”——5分钟内相同请求可以无限重放
- ❌ “把 access token / AppSecret 放 URL 参数里”——URL 会进访问日志、Referer、浏览器历史,token 应放 Authorization Header,AppSecret 永远不上传;一次性签名(绑定时间戳+nonce)放 URL 风险较小,如 S3 预签名 URL
- ❌ 用
String.equals比对签名——逐字节提前返回,存在计时攻击,应用MessageDigest.isEqual等常量时间比较