通过Postman实现专有云云解析API网关的请求签名与调试

本文涉及的产品
云原生 API 网关,700元额度,多规格可选
AI 网关免费试用,2900元额度,限量100份
简介: 本文主要使用Postman调试API网关的方法,文中使用Pre-request Script实现了阿里专有云云解析API网关的签名算法。

1.参考文档

以下两篇文档是我调试成功的基础,感激不尽。

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

云解析 DNS-调用方式-签名机制

2. 起因

需要使用云解析python sdk,同事已经写好了相关python脚本。我想借此机会了解一下restful api。

先是找到了postman这个工具,再者搜到了上面提到的两篇文章。当然还需要wireshark把python sdk的请求抓一下包,照猫画虎,构造postman中的结构。

3. 获取域名列表

3.1 Headers

image.png

3.2 Params

image.png

3.3 Pre-request Script

Date.prototype.format = function(format) {
       var date = {
              "M+": this.getUTCMonth() + 1,
              "d+": this.getUTCDate(),
              "h+": this.getUTCHours(),
              "m+": this.getUTCMinutes(),
              "s+": this.getUTCSeconds(),
              "q+": Math.floor((this.getMonth() + 3) / 3),
              "S+": this.getUTCMilliseconds()
       };
       if (/(y+)/i.test(format)) {
              format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
       }
       for (var k in date) {
              if (new RegExp("(" + k + ")").test(format)) {
                     format = format.replace(RegExp.$1, RegExp.$1.length == 1
                            ? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
              }
       }
       return format;

}

//填写实际使用的key和secret
var appKey = "******";
var appSecret = "**********";

//var md5 = calcMd5();
var nowDate = new Date();
var date = nowDate.toUTCString();
var nonce = createUuid();
var tstamp = nowDate.format('yyyy-MM-ddThh%3Amm%3AssZ');
pm.globals.set("tstamp", tstamp);

var textToSign = "";
textToSign += request.method + "&" + "%2F" + "&";
//textToSign += request.headers["accept"] + "\n";
//textToSign += md5 + "\n";
//textToSign += request.headers["content-type"] + "\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 + "&" )
var hash = CryptoJS.HmacSHA1(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')) {
//        const formParams = request.data.split("&");
//        formParams.forEach((p) => {
//            const ss = p.split('=');
//            params.set(ss[0], ss[1]);
//        })
//    }
    
    const ss = request.url.split('?');
    if (ss.length > 1 && ss[1]) {
        const queryParams = ss[1].split('&');
        queryParams.forEach((p) => {
            const ss = p.split('=');
            if(ss[0]==="Signature") return;
            if(ss[1]==="{{tstamp}}") ss[1]=tstamp;
            if(ss[1]==="{{Nonce}}") ss[1]=nonce;
            params.set(ss[0], ss[1]);
        })
    }
    
    var sortedKeys = Array.from(params.keys())
    sortedKeys.sort();
    
//    var l1 = ss[0].lastIndexOf('/');
//    var url = ss[0].substring(l1);
    var first = true;
    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;
    return encodeURIComponent(qs);
}

//function calcMd5() {
//    var contentType = request.headers["content-type"];
//    if (request.data && !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);
    });
}

这里的大多数脚本都是来源于前面提到的文档。
pm.globals.set设置的全局变量可以在params、headers、body等中引用,用两个大括号括起来。比如{{tstamp}}

最主要是签名规则,本例是把params中所有参数按key排序,连在一起,再签名。

3.4 post

image.png
左侧下拉菜单选择POST。地址框中要写api的地址,后面的参数都是自动填写的。

点右侧的send按钮,运气好的话,正确的reponse就出现了。

4.获取A记录

4.1 Headers

image.png

4.2 Params

image.png

4.3 Body

image.png
这里Id的值是53,是之前请求域名列表时返回的DomainId。

4.4 Pre-request Script

Date.prototype.format = function(format) {
       var date = {
              "M+": this.getUTCMonth() + 1,
              "d+": this.getUTCDate(),
              "h+": this.getUTCHours(),
              "m+": this.getUTCMinutes(),
              "s+": this.getUTCSeconds(),
              "q+": Math.floor((this.getMonth() + 3) / 3),
              "S+": this.getUTCMilliseconds()
       };
       if (/(y+)/i.test(format)) {
              format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
       }
       for (var k in date) {
              if (new RegExp("(" + k + ")").test(format)) {
                     format = format.replace(RegExp.$1, RegExp.$1.length == 1
                            ? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
              }
       }
       return format;

}
//填写自己的key和secret
var appKey = "******";
var appSecret = "**********";

//var md5 = calcMd5();
var nowDate = new Date();
var date = nowDate.toUTCString();
var nonce = createUuid();
var tstamp = nowDate.format('yyyy-MM-ddThh%3Amm%3AssZ');
pm.globals.set("tstamp", tstamp);

var textToSign = "";
textToSign += request.method + "&" + "%2F" + "&";
//textToSign += request.headers["accept"] + "\n";
//textToSign += md5 + "\n";
//textToSign += request.headers["content-type"] + "\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 + "&" )
var hash = CryptoJS.HmacSHA1(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"];
    console.log("request");
    console.log(request);
    console.log("request.data");
    console.log(request.data);
    console.log("pm.request");
    console.log(pm.request);
//    if (contentType && contentType.startsWith('application/x-www-form-urlencoded')) {
//        const formParams = request.data.split("&");
//        formParams.forEach((p) => {
//            const ss = p.split('=');
//            params.set(ss[0], ss[1]);
//        })
//    }

    console.log("body form:");
    for(var key in request.data) {
        var value = request.data[key];
        if(key==="RrSet") value=escape(value);
        console.log("key = "+key+", value = "+value);
        params.set(key,value);
    }
    
    const ss = request.url.split('?');
    if (ss.length > 1 && ss[1]) {
        const queryParams = ss[1].split('&');
        queryParams.forEach((p) => {
            const ss = p.split('=');
            if(ss[0]==="Signature") return;
            if(ss[1]==="{{tstamp}}") ss[1]=tstamp;
            if(ss[1]==="{{Nonce}}") ss[1]=nonce;
            params.set(ss[0], ss[1]);
        })
    }
    
    var sortedKeys = Array.from(params.keys())
    sortedKeys.sort();
    
//    var l1 = ss[0].lastIndexOf('/');
//    var url = ss[0].substring(l1);
    var first = true;
    var qs
    console.log("All params:");
    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;
//    return encodeURIComponent(qs);
    return escape(qs);
}

//function calcMd5() {
//    var contentType = request.headers["content-type"];
//    if (request.data && !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);
    });
}

5.更新A记录

5.1 Headers

image.png

5.2 Params

image.png

5.3 Body

image.png

5.4 Pre-request Script

Date.prototype.format = function(format) {
       var date = {
              "M+": this.getUTCMonth() + 1,
              "d+": this.getUTCDate(),
              "h+": this.getUTCHours(),
              "m+": this.getUTCMinutes(),
              "s+": this.getUTCSeconds(),
              "q+": Math.floor((this.getMonth() + 3) / 3),
              "S+": this.getUTCMilliseconds()
       };
       if (/(y+)/i.test(format)) {
              format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
       }
       for (var k in date) {
              if (new RegExp("(" + k + ")").test(format)) {
                     format = format.replace(RegExp.$1, RegExp.$1.length == 1
                            ? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
              }
       }
       return format;

}
//填写自己的key和scret
var appKey = "*******";
var appSecret = "********";

//var md5 = calcMd5();
var nowDate = new Date();
var date = nowDate.toUTCString();
var nonce = createUuid();
var tstamp = nowDate.format('yyyy-MM-ddThh%3Amm%3AssZ');
pm.globals.set("tstamp", tstamp);

var textToSign = "";
textToSign += request.method + "&" + "%2F" + "&";
//textToSign += request.headers["accept"] + "\n";
//textToSign += md5 + "\n";
//textToSign += request.headers["content-type"] + "\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 + "&" )
var hash = CryptoJS.HmacSHA1(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"];
    console.log("request");
    console.log(request);
    console.log("request.data");
    console.log(request.data);
    console.log("pm.request");
    console.log(pm.request);
//    if (contentType && contentType.startsWith('application/x-www-form-urlencoded')) {
//        const formParams = request.data.split("&");
//        formParams.forEach((p) => {
//            const ss = p.split('=');
//            params.set(ss[0], ss[1]);
//        })
//    }

    console.log("body form:");
    for(var key in request.data) {
        var value = request.data[key];
      //根据response中提示的签名字符串,body中form中的数组多进行了一次转义,把每个百分号多转义了一次
      if(key==="RrSet") value=escape(value);
        console.log("key = "+key+", value = "+value);
        params.set(key,value);
    }
    
    const ss = request.url.split('?');
    if (ss.length > 1 && ss[1]) {
        const queryParams = ss[1].split('&');
        queryParams.forEach((p) => {
            const ss = p.split('=');
            if(ss[0]==="Signature") return;
            if(ss[1]==="{{tstamp}}") ss[1]=tstamp;
            if(ss[1]==="{{Nonce}}") ss[1]=nonce;
            params.set(ss[0], ss[1]);
        })
    }
    
    var sortedKeys = Array.from(params.keys())
    sortedKeys.sort();
    
//    var l1 = ss[0].lastIndexOf('/');
//    var url = ss[0].substring(l1);
    var first = true;
    var qs
    console.log("All params:");
    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;
//    return encodeURIComponent(qs);
    return escape(qs);
}

//function calcMd5() {
//    var contentType = request.headers["content-type"];
//    if (request.data && !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);
    });
}

6.调试

6.1 充分利用wireshark

把sdk成功执行的请求抓出来,看header、body、parameter部分,在postman中配置成一样的

6.2 充分利用postman的console

利用console.log打印出变量值,注意打印对象值的时候不要把对象用加号和字符串连接进来。
比如

    console.log(request);
    console.log("request.data");
    console.log(request.data);
    console.log("pm.request");
    console.log(pm.request);

这些对象的结果非常清晰直观。

把textToSign打印出来,当签名有问题时,用于和服务器端返回的reponse中提示的签名字符串比较。

6.3 仔细看reponse中的提示

比如签名有问题时,提示消息中有"Message": "Specified signature is not matched with our calculation. server string to sign is:后面是签名字符串,看看和自己的testToSign是否一样

7.存在问题

偶尔会提示Specified signature is not matched,再点一下send就好了,没再找原因了。

相关文章
|
2月前
|
JSON JavaScript 测试技术
用Postman玩转电商API:一键测试+自动化请求教程
Postman 是电商 API 测试的高效工具,涵盖基础配置、自动化测试、环境管理与请求自动化,助你快速提升开发效率。
|
5月前
|
XML 前端开发 测试技术
如何使用 Postman 发送 POST XML 请求?
使用 Postman 发送带有 XML 数据的 POST 请求。我们将引导您完成将 XML 数据有效发送到 Web 服务或 API 的步骤,使处理这种常见数据格式变得简单易行。
|
2天前
|
人工智能 数据可视化 测试技术
Postman 性能测试教程:快速上手 API 压测
本文介绍API上线后因高频调用导致服务器告警,通过Postman与Apifox进行压力测试排查性能瓶颈。对比两款工具在批量请求、断言验证、可视化报告等方面的优劣,探讨API性能优化策略及行业未来发展方向。
Postman 性能测试教程:快速上手 API 压测
|
6月前
|
XML JSON API
掌握 Postman:高级 GET 请求技术与响应分析
本指南详细讲解了如何在 Postman 中发送 GET 请求并解析 API 响应,帮助开发者提升 API 测试与开发能力。Postman 是一款强大的工具,可简化请求发送和响应分析流程,并支持团队协作及多版本管理。通过创建集合、配置请求参数、设置身份验证与请求头等步骤,开发者能够高效测试 API。同时,理解响应体、Cookie、响应头等内容有助于深入分析 API 行为,确保高质量的软件交付。掌握 Postman 不仅提高效率,还能加深对 Web 通信机制的理解。
|
5月前
|
数据可视化 测试技术 API
JMeter、Apipost 与 Postman 的 API 测试对比:为什么 APIPost 是更聪明的选择
API测试如同筹备一场晚宴,选对工具至关重要。JMeter功能强大但上手难,适合专业用户;Postman简单易用,但在复杂场景和团队协作中表现有限;而Apipost则是一款智能高效的“厨房神器”。它性能测试轻松、结果清晰、学习门槛低,并且能一键集成CI/CD流程。对于追求效率与便捷的团队而言,Apipost无疑是更优选择,让API测试如同五星大厨烹饪般丝滑流畅。
|
5月前
|
存储 前端开发 数据可视化
Postman vs. Apifox 用于 API 测试全面对比
寻找一款可靠的 API 测试工具?这份对比分析将深入探讨 Postman 和 Apifox 的功能和特性。了解哪款工具最适合您的 API 测试需求。
|
5月前
|
监控 安全 测试技术
选择Postman免费版还是付费版,进行 API 测试呢?
深入了解 Postman 免费版和付费版的细节,看看哪一个更适合您的 API 需求。
|
6月前
|
网络协议 API 开发者
深入解密 :Postman、Apipost和Apifox API 协议与工具选择
作为全栈开发者,每天与API打交道是常态。本文总结了多年经验,深入解析常见API协议(HTTP(s)、SSE、gRPC、WebSocket、Socket.IO)及其适用场景,并对比三款主流调试工具(Postman、Apipost、ApiFox)。从基础特性到高级应用,帮助开发者根据需求选择最优方案,提升效率,让开发更顺畅!
|
6月前
|
数据可视化 JavaScript 前端开发
利用Postman和Apipost进行API测试的实践与优化-动态参数
在API测试中,Postman和Apipost是常用的工具。Postman内置变量功能有限,面对复杂场景时需编写JavaScript脚本,增加了维护成本。而Apipost提供丰富的内置变量、可视化动态值配置和低代码操作,支持生成真实随机数据,如邮箱、手机号等,显著提升测试效率和灵活性。对于复杂测试场景,Apipost是更好的选择,能有效降低开发与维护成本,提高测试工作的便捷性和可维护性。
|
6月前
|
人工智能 测试技术 API
Windows用户必备:Postman v11详细安装指南与API测试入门教程(附官网下载
Postman是全球领先的API开发与测试工具,支持REST、SOAP、GraphQL等协议调试。2025年最新版v11新增AI智能生成测试用例、多环境变量同步等功能,适用于前后端分离开发、自动化测试、接口文档自动生成及团队协作共享API资源。本文详细介绍Postman的软件定位、核心功能、安装步骤、首次配置、基础使用及常见问题解答,帮助用户快速上手并高效利用该工具进行API开发与测试。

推荐镜像

更多
  • DNS