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

简介: 本文主要使用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就好了,没再找原因了。

相关文章
|
JSON 安全 Java
什么是用于REST API的JWT Bearer令牌以及如何通过代码和工具进行调试
在Web开发中,保护REST API至关重要,而JSON Web令牌(JWT)特别是JWT Bearer令牌,是一种高效方法。它通过紧凑、自包含的结构实现安全信息交换,提升用户体验。本文探讨JWT Bearer的基本概念、结构与实现,包括在Java中的应用步骤,以及使用Apipost和cURL进行测试的方法。JWT优势明显:无状态、互操作性强,适用于分布式系统。掌握JWT Bearer,可助开发者构建更安全、高效的API解决方案。
|
人工智能 JSON 自然语言处理
AI 程序员的4个分身 | 代码生成专家+注释精灵+API集成助手+智能调试伙伴
AI 程序员的4个分身 | 代码生成专家+注释精灵+API集成助手+智能调试伙伴
685 35
|
安全 API Go
如何实现和调试REST API中的摘要认证(Digest Authentication)
本文介绍如何实现和调试REST API中的摘要认证(Digest Authentication),涵盖其原理、优势及Java和Go语言的实现示例。摘要认证通过哈希算法处理密码,避免明文传输风险,并使用nonce防止重放攻击,确保数据完整性。文中还提供了Postman、cURL和Insomnia等工具的测试方法,帮助开发者轻松验证API的安全性。总结指出,摘要认证相比基本认证更安全,适合需要高安全性的API应用。
|
安全 Java API
什么是用于 REST API 的 Bearer Token以及如何通过代码和工具进行调试
Bearer Token 是一种基于 OAuth 2.0 的身份验证机制,广泛应用于 REST API 的授权访问中。它通过在 HTTP 请求头中传递令牌,确保用户凭据安全传输并验证。本文深入解析了 Bearer Token 的概念、实现步骤及调试方法,包括其无状态特性、灵活性与安全性优势。同时,提供了 Java 实现示例和使用 Apipost、cURL 等工具测试的实践指导,帮助开发者掌握这一核心技能,保障 API 系统的安全与高效运行。
|
机器学习/深度学习 人工智能 自然语言处理
企业级API集成方案:基于阿里云函数计算调用DeepSeek全解析
DeepSeek R1 是一款先进的大规模深度学习模型,专为自然语言处理等复杂任务设计。它具备高效的架构、强大的泛化能力和优化的参数管理,适用于文本生成、智能问答、代码生成和数据分析等领域。阿里云平台提供了高性能计算资源、合规与数据安全、低延迟覆盖和成本效益等优势,支持用户便捷部署和调用 DeepSeek R1 模型,确保快速响应和稳定服务。通过阿里云百炼模型服务,用户可以轻松体验满血版 DeepSeek R1,并享受免费试用和灵活的API调用方式。
1082 12
|
JSON 自然语言处理 前端开发
WebSocket调试工具深度对比:Postman与Apipost功能实测解析
本文深入对比了Postman与Apipost两款WebSocket调试工具。作为实时通讯系统工程师,作者在开发智能客服系统时遇到了传统工具调试复杂、文档管理不便的问题。通过引入Apipost的智能连接池、消息分组管理和自动化文档生成等功能,实现了多环境自动切换、消息分类和接口文档自动生成,极大提升了调试效率和团队协作效果。最终,使用Apipost使接口调试时间减少40%,文档维护成本降低70%,跨团队沟通效率提升50%。
|
数据可视化 测试技术 API
前后端分离开发:如何高效调试API?有工具 vs 无工具全解析
在前后端分离的开发模式中,API 调试的效率直接影响项目的质量和交付速度。通过本文的对比分析,我们可以看到无工具调试模式虽具备灵活性和代码复用能力,但在操作便利性和团队协作上稍显不足。而传统的外部调试工具带来了可视化、高效协作与扩展性,却可能存在工具切换带来的开发链路断层问题。Apipost-Hepler 融合了两者的优势,让开发者无需离开熟悉的 IDEA 环境,就能享受可视化调试工具的强大功能。
697 5
|
JSON 监控 物联网
#WebSocket 调试全攻略:Postman、Apipost和Apifox核心解析、工具选择与对比!
WebSocket 是一种现代化的全双工通信协议,允许客户端和服务端通过持久连接实时双向传输数据。它适用于即时通讯、实时通知、金融行情、在线协作、物联网等场景。调试 WebSocket 时,工具应具备握手管理、实时消息收发、自定义 Header、消息大小监控、分组管理、多连接支持等功能。
1246 1
|
前端开发 程序员 API
(1)深度对比:Apipost vs Apifox (1):产品背景及API简单调试
API调试工具是每个开发者不可或缺的帮手。国外的Postman功能强大但不够“中国特色”,收费也让人望而却步。国内的Apipost和Apifox应运而生,旨在解决本地化需求。 Apipost由一位不满Postman的开发者亲手打造,界面直观、操作流畅,支持离线使用,特别适合需要频繁离线操作的场景。Apifox则致力于整合接口生命周期的各个流程,提供一体化解决方案,初次上手稍显复杂,但功能全面且潜力巨大。 两者各有千秋:Apipost更适合快速上手和离线使用,Apifox则在团队管理和功能整合方面表现突出。选择哪款工具,取决于你的具体需求和使用习惯。
|
JSON 前端开发 安全
WebSocket调试工具深度对比:Postman与Apipost功能实测解析
如果你在寻找既能搞定WebSocket调试,又能完美管理文档的工具,不妨试试Apipos!
756 1

推荐镜像

更多
  • DNS