通过Postman实现API网关的请求签名与调试

本文涉及的产品
云原生 API 网关,700元额度,多规格可选
简介: Postman是一个非常强大的HTTP发包测试工具, 目前Postman已经提供了Windows/Mac/Linux系统的客户端的下载,使用很方便。不过API网关的调试,需要对HTTP请求进行签名才能调用,无法使用简单的curl等发包工具完成,但我们可以使用Postman工具提供的Pre-request Script脚本来实现API网关的签名功能,实现API的调试功能。

1. 前言

Postman是一个非常强大的HTTP发包测试工具, 目前Postman已经提供了Windows/Mac/Linux系统的客户端的下载,使用很方便。不过API网关的调试,需要对HTTP请求进行签名才能调用,无法使用简单的curl等发包工具完成,但我们可以使用Postman工具提供的Pre-request Script脚本来实现API网关的签名功能,实现API的调试功能。

2. API网关签名算法介绍

API网关的签名机制详细可以参考请求签名说明文档,这里简要介绍一下。

API网关的签名需要通过API网关的AppKey和AppSecret进行,Key/Secret可以在API网关的控制台上获得,并确保API已经发布,并且针对特定的APP做了授权操作。

针对一个普通请求,API网关的签名过程如下

2.1. 添加以下头用于辅助签名与安全认证

- Date: 日期头
- X-Ca-Key:{AppKey} 
- X-Ca-Nonce:API调用者生成的 UUID, 实现防重放功能
- Content-MD5: 当请求Body为非Form表单时,用于校验Body是否被篡改, 

2.2. 组织需要签名的字符串StringToSign

{HTTPMethod} + "\n" + 
{Accept} + "\n" +
{Content-MD5} + "\n" 
{Content-Type} + "\n" + 
{Date} + "\n" + 
{SignatureHeaders} + 
{UrlToSign}
  • Accept、Content-MD5、Content-Type、Date 如果为空也需要添加换行符”n”
  • 只有From为非表单的方式才需要计算Content-MD5,计算方法为base64Encode(md5(body.getBytes("UTF-8"))
  • SignatureHeaders: 以{HeaderName}:{HeaderValue} + "n"的方式按照字符串顺序从小到大顺序添加, 建议加入签名的头为X-Ca-Key,X-Ca-Nonce, 其他头客户端实现可自行选择是否加入签名。
  • UrlToSign: 将所有的Form字段和QueryString字段放在一起按照Name进行排序,如果Content-Type不是application/x-www-form-urlencoded类型则不拆开Form字段。将排序好的键值对加到Path后面得到UrlToSign, 例如请求/demo?c=1&a=2, Form为b=3则UrlToSign=/demo?a=2&b=3&c=1

2.3. 计算签名并附加签名相关Headers

目前推荐使用HMacSHA256算法计算签名,签名的计算需要appSecret,计算方法为:signature = base64(hmacSHA256(stringToSign.getBytes("UTF-8), appSecret)), 计算完毕后还需要添加以下Headers:

  • 添加Header: X-Ca-Siguature:{signature}
  • 添加Header: X-Ca-SignatureMethod:HmacSHA256
  • 添加Header: X-Ca-SignatureHeaders:X-Ca-Key,X-Ca-Nonce

2.4. 签名错误排查方法

  • 当签名校验失败时,API网关会将服务端的StringToSign放到HTTP应答的Header中返回到客户端,Key为:X-Ca-Error-Message,只需要将本地计算的StringToSign与服务端返回的StringToSign进行对比即可找到问题,注意服务端返回的StringToSign将回车替换为了#;
  • 如果服务端与客户端的签名串是一致的,请检查用于签名计算的密钥是否正确;

3. 使用Pre-request Script实现签名算法

根据上一节的描述,实现API网关调试的关键问题在于如何实现请求签名,Postman提供了可以通过JavaScript进行定制的, 通过阅读Pre-request Script的开发文档, 我们可以通过Pre-request Script脚本实现API网关的签名功能。

注意:本节的代码请使用POSTMAN Version 7.2.0以上版本

3.1. 使用全局变量预制签名需要添加的头

不过目前Postman不允许直接在脚本中修改请求,所以我们只能使用预制签名头并使用全局变量赋值的方式完成签名头的添加,我们将需要签名的头都预制在Postman的请求Header中,可以通过Bulk Edit模式实现添加,Bulk Edit请参照下图进行切换

d727c94c4c8e06ab51617de54a6cab63

切换为Bulk Edit模式后,可以将如下字符串复制粘贴到输入框当中,被{{}}括住的就是Postman的全局变量,我们在脚本中实现替换。Form内容的可以不添加Content-MD5头

Date:{{Date}}
Content-MD5:{{Md5}}
X-Ca-Nonce:{{Nonce}}
X-Ca-Key:{{AppKey}}
X-Ca-Signature:{{Signature}}
X-Ca-SignatureMethod:HmacSHA256
X-Ca-Signature-Headers:{{SignatureHeaders}}

粘贴后效果如图

bb92849f4ac664de833fe110a9f4f032

3.2. 使用Pre-request Script脚本实现签名功能

点击红圈圈住的位置,可以输入Pre-request Script,请复制粘贴下面提供的代码到文本框当中

注意:本节的代码请使用POSTMAN Version 7.2.0以上版本

311199754a861386c03e41f361ecc5ac

var appKey = "YOUR APPKEY";
var appSecret = "YOUR APPCODE";
var md5 = calcMd5();
var dateObject = Date;
var date = dateObject.toLocaleString();
var nonce = createUuid();
var textToSign = "";
var accept = "*/*";
var contentType = "";
console.log("request" + JSON.stringify(request));
if(request.headers["accept"]){
    accept = request.headers["accept"];
}
if(request.headers["content-type"]){
    contentType = request.headers["content-type"];
}
textToSign += request.method + "\n";
textToSign += accept + "\n";
textToSign += md5 + "\n";
textToSign += contentType + "\n";
textToSign += date + "\n";
var headers = headersToSign();
var signatureHeaders;
var sortedKeys = Array.from(headers.keys()).sort()
for (var headerName of sortedKeys) {
    textToSign += headerName + ":" + headers.get(headerName) + "\n";
    signatureHeaders = signatureHeaders ? signatureHeaders + "," + headerName : headerName;
}
textToSign += urlToSign();
console.log("textToSign\n" + textToSign.replace(/\n/g, "#"));
var hash = CryptoJS.HmacSHA256(textToSign, appSecret)
console.log("hash:" + hash)
var signature = hash.toString(CryptoJS.enc.Base64)
console.log("signature:" + signature)
pm.globals.set('AppKey', appKey);
pm.globals.set('Md5', md5);
pm.globals.set("Date", date);
pm.globals.set("Signature", signature);
pm.globals.set("SignatureHeaders", signatureHeaders);
pm.globals.set("Nonce", nonce);
function headersToSign() {
    var headers = new Map();
    for (var name in request.headers) {
        name = name.toLowerCase();
        if (!name.startsWith('x-ca-')) {
            continue;
        } 
        if (name === "x-ca-signature" || name === "x-ca-signature-headers" || name == "x-ca-key" || name === 'x-ca-nonce') {
            continue;
        }
        var value = request.headers[name];
        headers.set(name, value);
    }
    headers.set('x-ca-key', appKey);
    headers.set('x-ca-nonce', nonce);
    return headers;
}
function urlToSign() {
    var params = new Map();
    var contentType = request.headers["content-type"];
    if (contentType && contentType.startsWith('application/x-www-form-urlencoded')) {
       for(x in request.data){
           params.set(x, request.data[x]);
       }
    }
    var queryParam = pm.request.url.query.members;
    console.log("request.url" + JSON.stringify(pm.request.url))
    for (let i in queryParam) {
        params.set(queryParam[i].key, queryParam[i].value);
    }
    var sortedKeys = Array.from(params.keys())
    sortedKeys.sort();
    var url = "";
    for(var k of pm.request.url.path){
        url = url + "/" + k;
    }
    var qs;
    for (var k of sortedKeys) {
        var s = k + "=" + params.get(k);
        qs = qs ? qs + "&" + s : s;
        console.log("key=" + k + " value=" + params.get(k));
    }
    return qs ? url + "?" + qs : url;
}
function calcMd5() {
    var contentType = String(request.headers["content-type"]);
    console.log("data" + JSON.stringify(request.data));
    if (!JSON.stringify(request.data).startsWith('{}') && !contentType.startsWith('application/x-www-form-urlencoded')) {
        var data = request.data;
        var md5 = CryptoJS.MD5(data);
        var md5String = md5.toString(CryptoJS.enc.Base64);
        console.log("data:" + data + "\nmd5:" + md5String);
        return md5String;
    } else {
        return "";
    }
}
function createUuid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
}

接下来我们就可以实现API网关的调试了。

注意本节代码仅供用户参考。目前有用户反馈POSTMAN在WINDOWS平台运行本脚本时会出现“Can not get any response”的情况,建议用户尽量在MAC环境下使用POSTMAN进行调试。

目录
相关文章
|
2月前
|
缓存 监控 API
抖音抖店 API 请求获取宝贝详情数据的调用频率限制如何调整?
抖音抖店API请求获取宝贝详情数据的调用频率受限,需遵循平台规则。开发者可通过提升账号等级、申请更高配额、优化业务逻辑(如缓存数据、异步处理、批量请求)及监控调整等方式来应对。
|
2月前
|
缓存 负载均衡 API
抖音抖店API请求获取宝贝详情数据、原价、销量、主图等参数可支持高并发调用接入演示
这是一个使用Python编写的示例代码,用于从抖音抖店API获取商品详情,包括原价、销量和主图等信息。示例展示了如何构建请求、处理响应及提取所需数据。针对高并发场景,建议采用缓存、限流、负载均衡、异步处理及代码优化等策略,以提升性能和稳定性。
|
15天前
|
JSON API 数据格式
携程API接口系列,酒店景点详情请求示例参考
携程API接口系列涵盖了酒店预订、机票预订、旅游度假产品预订、景点门票预订等多个领域,其中酒店和景点详情请求是较为常用的功能。以下提供酒店和景点详情请求的示例参考
|
2月前
|
JavaScript 前端开发 Java
多种语言请求API接口方法
每种语言和库的选择取决于具体需求、项目环境以及个人偏好。了解这些基本方法,开发者就可以根据项目需求选择合适的语言和库来高效地与API交互。
39 1
|
2月前
|
存储 数据可视化 JavaScript
可视化集成API接口请求+变量绑定+源码输出
可视化集成API接口请求+变量绑定+源码输出
54 4
|
3月前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
2月前
|
缓存 网络协议 API
【Azure 环境】请求经过应用程序网关,当响应内容大时遇见504超时报错
应用程序网关的响应缓冲区可以收集后端服务器发送的全部或部分响应数据包,然后再将它们发送给客户端。 默认在应用程序网关上启用响应缓冲,这对于适应缓慢的客户端很有用。
|
2月前
|
API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
28 0
|
17天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
2月前
|
Cloud Native API
微服务引擎 MSE 及云原生 API 网关 2024 年 9 月产品动态
微服务引擎 MSE 及云原生 API 网关 2024 年 9 月产品动态。