API签名认证设计

设计目标

  • 安全性:确保签名算法足够安全,防止篡改和重放攻击。
  • 可扩展性:算法应支持不同的数据格式和传输协议。
  • 易用性:算法应易于实现和集成,方便开发者使用。

算法设计

签名参数

  • Access Key:每个API调用者持有的唯一标识。
  • Secret Key:与Access Key关联的密钥,用于生成签名。
  • Timestamp:请求发出的时间戳,用于防止重放攻击。
  • Nonce:随机数,用于防止重放攻击。
  • 请求方法:HTTP请求方法(如GET、POST等)。
  • 请求路径:HTTP请求的相对路径。
  • 请求参数:HTTP请求的参数,包括查询参数和请求体。

签名生成步骤

  1. 排序参数:将请求参数(包括请求方法、请求路径、请求参数等)按字典顺序排序。
  • Header参数:x-ta-access-key、x-ta-timestamp、x-ta-nonce
  • 查询参数:GET方法中查询参数
  • 表单参数:POST方法中参数,使用JSON格式,需要JSON数据按照key平铺后设置字典key
    • 对象字段通过"."拼接,如user.name
    • 数组字段通过"[0]"拼接,如files[0]
  • 排序方法:通过平铺后的key,按照ASCII升序排列
  • 排序参数按照urlencode方式进行拼接
  1. 拼接字符串:将排序后的参数拼接成一个字符串。
  • 拼接规则:Method + 空格 + 请求路径 + 空格 + 排序后的请求参数
  1. 生成签名:使用Secret Key对拼接后的字符串进行HMAC-SHA256加密。
  2. 添加签名到请求:将生成的签名作为请求头的一部分发送。
  • 请求头为signature

签名验证步骤

  1. 解析请求:从请求中提取、Timestamp、Nonce等参数。
  2. 重新生成签名:使用提取的参数和Secret Key重新生成签名。
  3. 比较签名:将重新生成的签名与请求中的签名进行比较,验证是否一致。
  4. 检查时间戳:检查Timestamp是否在有效的时间范围内,防止重放攻击。

代码实现

  • 平铺json数据
  • 签名算法

Java版

    public static Map<String, Object> flattenMap(Map<String, Object> map, String parentKey, String separator, TreeMap<String, Object> sortedMap) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            String newKey = parentKey.isEmpty() ? key : parentKey + separator + key;

            if (value instanceof Map) {
                flattenMap((Map<String, Object>) value, newKey, separator, sortedMap);
            } else if (value instanceof Collection) {
                int index = 0;
                for (Object item : (Collection<?>) value) {
                    if (item instanceof Map) {
                        flattenMap((Map<String, Object>) item, newKey + "[" + index + "]", separator, sortedMap);
                    } else {
                        sortedMap.put(newKey + "[" + index + "]", item);
                    }
                    index++;
                }
            } else {
                sortedMap.put(newKey, value);
            }
        }
        return sortedMap;
    }

Python版

def flatten_dict(d, parent_key='', sep='_', include_lists=True):
    items = []
    for k, v in d.items():
        new_key = f"{parent_key}{sep}{k}" if parent_key else k
        if isinstance(v, dict):
            items.extend(flatten_dict(v, new_key, sep=sep).items())
        elif include_lists and isinstance(v, list):
            for i, item in enumerate(v):
                sub_key = f"{new_key}_{i}" if new_key else str(i)
                if isinstance(item, dict):
                    items.extend(flatten_dict(item, sub_key, sep=sep).items())
                else:
                    items.append((sub_key, item))
        else:
            items.append((new_key, v))
    return dict(items)

Nodejs版

// 基于qs第三方库
const qs = required('qs')

const flatParams = qs.stringify(obj, {
  allowDots: true,
  encode: false
})

问题记录

使用qs排序平铺参数

在qs中,排序平铺参数时,数组参数顺序为:files[0]、files[1]、files[10]、files[2],按照ASCII排序,files[10] < files[1]

posted @ 2024-11-19 14:45  艳沐石  阅读(0)  评论(0编辑  收藏  举报