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),适用于服务器到服务器的场景。

OAuth2.0 客户端凭证模式流程 商户业务系统 (客户端) 授权服务器 (Auth Server) 资源服务器 (评分API) ① client_id + client_secret ② 返回 access_token ③ 携带 access_token 请求评分API ④ 验证token ⑤ 返回评分结果 说明:access_token 有效期通常为2小时,过期后需重新获取。建议使用短时效token + refresh_token 机制。
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 签名算法流程

请求签名生成与校验流程 客户端(签名生成) ① 收集参数:timestamp + nonce + 业务参数 ② 按字典序排序参数,拼接字符串 ③ 拼接AppSecret,计算HMAC-SHA256 ④ 生成签名,放入请求头 服务端(签名校验) ⑤ 提取请求头中的签名和参数 ⑥ 使用相同算法重新计算签名 ⑦ 比对签名是否一致 ⑧ 校验通过 / 拒绝请求 请求传输

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的商户。