4. 接口安全设计:API密钥、OAuth2.0、IP白名单、请求签名机制
商户风险评分API承载着敏感的交易与商户数据,接口安全是整体设计的基石。本章围绕四层纵深防御体系展开:API密钥用于身份标识,OAuth2.0用于授权委托,IP白名单用于网络边界控制,请求签名机制用于数据完整性校验。四者协同,构建从传输层到应用层的完整防护。
设计原则:安全策略应遵循“最小权限、默认拒绝、纵深防御”三大原则。任何单一机制都存在被绕过的风险,必须组合使用。
4.1 API密钥(API Key)
API密钥是调用方身份的静态凭证,通常由服务端颁发的一对字符串(AppKey + AppSecret)。AppKey用于标识调用方身份,AppSecret用于签名计算,不应在网络中明文传输。
| 字段 | 说明 | 安全要求 |
|---|---|---|
| AppKey | 商户唯一标识,明文传输 | 不可包含敏感信息,建议UUID格式 |
| AppSecret | 签名密钥,仅服务端与商户持有 | 长度≥32位,定期轮换(建议90天) |
| 状态管理 | 启用/禁用/过期 | 支持即时吊销,防止泄露后滥用 |
安全警告:严禁将AppSecret硬编码在客户端代码、前端页面或公开仓库中。移动端或浏览器端应使用后端代理转发请求,避免密钥泄露。
4.2 OAuth2.0 授权委托
当第三方系统需要代表商户访问风险评分API时,OAuth2.0提供了安全的授权委托机制。本接口推荐使用客户端凭证模式(Client Credentials Grant),适用于服务器到服务器的场景。
Token 生命周期管理:
- 获取:POST /oauth/token,参数:grant_type=client_credentials
- 使用:HTTP Header: Authorization: Bearer {access_token}
- 刷新:支持 refresh_token 机制,避免频繁重新授权
- 吊销:商户异常时,服务端可立即失效所有token
4.3 IP白名单
IP白名单是网络层的访问控制手段,仅允许预设的IP地址或IP段访问API。对于服务端API,这是阻断未授权请求的第一道防线。
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| 白名单粒度 | 商户级别 | 每个商户独立配置,互不影响 |
| 支持格式 | 单IP / CIDR段 | 如 192.168.1.100 或 10.0.0.0/8 |
| 最大条目 | 20个/商户 | 防止白名单过于宽泛 |
| 变更生效 | 实时生效 | 修改后无需重启服务 |
注意事项:
- IP白名单不能作为唯一安全措施,IP可能被伪造(内网场景除外)。
- 对于使用负载均衡或云服务的商户,需确认出口IP是否固定。
- 建议配合请求签名机制使用,形成双重校验。
4.4 请求签名机制
请求签名机制用于验证请求的完整性、真实性和防重放攻击。商户使用AppSecret对请求参数进行签名,服务端使用相同的密钥和算法进行校验。
4.4.1 签名算法流程
4.4.2 签名参数规范
| 参数 | 位置 | 说明 | 示例 |
|---|---|---|---|
| timestamp | 请求头/参数 | Unix毫秒时间戳,服务端校验±5分钟 | 1712567890123 |
| nonce | 请求头/参数 | 随机字符串,防止重放攻击 | a1b2c3d4e5f6 |
| sign | 请求头 | 签名值,Base64编码 | q1w2e3r4t5y6u7i8o9p0 |
| sign_method | 请求头 | 签名算法标识 | HMAC-SHA256 |
4.4.3 签名生成示例(Java)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.TreeMap;
public class SignUtil {
public static String generateSign(TreeMap<String, String> params, String appSecret) throws Exception {
// 1. 按字典序拼接参数
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
sb.deleteCharAt(sb.length() - 1);
// 2. 拼接AppSecret
String stringToSign = sb.toString() + appSecret;
// 3. HMAC-SHA256 计算
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(appSecret.getBytes("UTF-8"), "HmacSHA256");
mac.init(secretKey);
byte[] hash = mac.doFinal(stringToSign.getBytes("UTF-8"));
// 4. Base64编码
return Base64.getEncoder().encodeToString(hash);
}
}
4.4.4 服务端校验逻辑
public boolean verifySign(HttpRequest request, String appSecret) {
// 1. 提取参数
String timestamp = request.getHeader("timestamp");
String nonce = request.getHeader("nonce");
String sign = request.getHeader("sign");
// 2. 时间戳校验(±5分钟)
long now = System.currentTimeMillis();
if (Math.abs(now - Long.parseLong(timestamp)) > 300000) {
return false; // 请求过期
}
// 3. nonce去重(Redis缓存,有效期5分钟)
if (redis.exists("nonce:" + nonce)) {
return false; // 重复请求
}
redis.setex("nonce:" + nonce, 300, "1");
// 4. 重新计算签名并比对
TreeMap<String, String> params = extractParams(request);
String expectedSign = SignUtil.generateSign(params, appSecret);
return expectedSign.equals(sign);
}
防重放攻击关键点:
- timestamp:限制请求的有效时间窗口(推荐±5分钟),过期请求直接拒绝。
- nonce:每个nonce只能使用一次,服务端需记录已使用的nonce直到时间窗口过期。
- 组合使用:timestamp + nonce 共同确保请求的唯一性和时效性。
4.5 四层安全机制协同
| 安全层 | 防护目标 | 绕过难度 | 组合效果 |
|---|---|---|---|
| API密钥 | 身份标识 | 中(密钥泄露则失效) | 基础身份认证 |
| OAuth2.0 | 授权委托 | 高(需破解token) | 细粒度权限控制 |
| IP白名单 | 网络边界 | 中(IP可伪造) | 缩小攻击面 |
| 请求签名 | 数据完整性 | 极高(需密钥+算法) | 防篡改、防重放 |
最佳实践:对于高安全等级的商户风险评分API,建议同时启用API密钥 + OAuth2.0 + 请求签名机制。IP白名单作为可选增强,适用于已知固定出口IP的商户。