公共请求头验签说明
易荟通OPEN API 是使用 HTTP 并遵循 REST 原则设计的 Web 服务接口,您可以使用几乎任何客户端和任何编程语言与 REST API 进行交互。通过发送简单的 HTTP POST 请求就可以轻松接入使用。
统一请求格式#
BASE URL:#
https://gateway.yihuitong.topURL 格式:#
/coll-openapi/{resource}/{function}说明:#
- {resource}为资源名,通常对应一类 API
- {function}为该资源提供的操作方法
比如获取呼叫状态的 url 为:https://gateway.yihuitong.top/coll-openapi/call/record/callReport 表示调用呼叫的状态方法,返回 json 格式的字符串。 我们目前已经提供的接口,请参考 API。
HTTP 头信息:#
Accept:application/json;charset=utf-8; Content-Type:application/json;charset=utf-8; X-SIGNATURE: $signature; X-APIKEY: $APIKEY; X-TIMESTAMP: $timestamp; X-NONCE: $nonce;**说明#
上面带 $ 符号参数为变量对应下面表格说明
| 变量名称 | 说明 |
|---|---|
| $signature | 变量是接口验签值计算公式 signature = HMAC-SHAx-HEX(secret_key, signing_string) 从公式可以看出,想要获得签名需要得到 secret_key 和 signing_string 两个参数。 其中 secret_key 从 控制台->账号设置->APIKEY->secretKey 获取,signing_string 的计算公式为 signing_string = HTTP Method + \n + HTTP URI + \n + $APIKEY + \n + $timestamp + \n + $nonce + \n + canonical_query_string + \n + json_body |
| $APIKEY | 变量从 控制台->账号设置->APIKEY->apikey 获取 |
| $timestamp | 当前接口请求时的时间戳(精度到秒级) 跟易荟通服务器时间相差不能超过10秒 |
| $nonce | 变量是防重机制,请求时上次当前请求唯一ID; 推荐: 32-UUID、分布式ID |
- HTTP Method:指 HTTP 协议中定义的 GET、PUT、POST 等请求方法,必须使用全大写的形式。
- HTTP URI:要求必须以“/”开头,不以“/”开头的需要补充上,空路径为“/”。
- APIKEY: 请求头中的 X-APIKEY
- timestamp: 请求头中的 X-TIMESTAMP
- nonce: 请求头中的 X-NONCE
- canonical_query_string: 是对于 URL 中的 query( query 即 URL 中 ? 后面的 key1=valve1&key2=valve2 字符串)或者 Content-Type:application/x-www-form-urlencoded 的时候 body 里面的值,进行编码后的结果参数需要进行 url encode 编码 url_encode(key) + "=" + url_encode(value) 的形式,每一项转换后,以 key 按照字典顺序( ASCII 码由小到大)排序,并使用 & 符号连接起来,生成相应的 canonical_query_string 。 如果 没有query,canonical_query_string + \n 就不需要拼接上去
- json_body: POST 请求 Content-Type:application/json 的时候 json_body 就是 body 中的json请求数据
- HMAC-SHAx-HEX: 加密算法是 HmacSHA256 。 加密完后的字节数组需要在通过 Base64 进行编码
代码案例:#
根据下面的案例算出来headers的值是: {X-NONCE=bc9efee185e64ab9bc0b07a2785c4660, X-TIMESTAMP=1626856279, X-SIGNATURE=HB78nqGoplcCgZGInTYzEPjGyVy9/sm1uxQotqxo/6s=, X-APIKEY=123456789}
- Java
- Python
- C#
- PHP
import java.io.UnsupportedEncodingException;import java.net.URLEncoder;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.TreeMap;import java.util.stream.Collectors;
import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;import javax.xml.bind.DatatypeConverter;
import com.alibaba.fastjson.JSON;
public class SignatureCase { public static void main(String[] args) { @SuppressWarnings("unused") String callReportUrl = "https://gateway.yihuitong.top/coll-openapi/call/record/callReport"; String nonce = "bc9efee185e64ab9bc0b07a2785c4660"; Integer timestamp = 1626856279; String apikey = "123456789"; String secretKey = "1234567890"; String method = "GET"; String path = "/coll-openapi/call/record/callReport"; String contentType = ""; Map<String, Object> parameters = new HashMap<>(); parameters.put("callId","1234"); StringBuilder sb = new StringBuilder(); sb.append(method).append("\n") .append(path).append("\n") .append(apikey).append("\n") .append(timestamp).append("\n") .append(nonce).append("\n"); if(parameters!=null && !parameters.isEmpty()) { if("json".equals(contentType) ) { sb.append(JSON.toJSONString(parameters)).append("\n"); }else { String canonical_query_string = formatUrlMap(parameters, true, false); sb.append(canonical_query_string).append("\n"); } } try { Mac hasher = Mac.getInstance("HmacSHA256"); hasher.init(new SecretKeySpec(secretKey.getBytes(), "HmacSHA256")); byte[] hash = hasher.doFinal(sb.toString().getBytes()); Map<String,String> headers = new HashMap<String, String>(); headers.put("X-SIGNATURE", DatatypeConverter.printBase64Binary(hash)); headers.put("X-APIKEY", apikey); headers.put("X-TIMESTAMP", timestamp.toString()); headers.put("X-NONCE", nonce); System.out.println(headers +": "+ headers); } catch (NoSuchAlgorithmException | InvalidKeyException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static String formatUrlMap(Map<String, Object> paraMap,boolean urlEncode,boolean keyToLower) { if(paraMap==null || paraMap.isEmpty()) { return null; } if(!(paraMap instanceof TreeMap)) { paraMap = new TreeMap<String, Object>(paraMap); } return paraMap.entrySet().stream().map(entry -> { String key = entry.getKey(); Object value = entry.getValue(); if(urlEncode) { try { key = URLEncoder.encode(key, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } if(value!=null && !"".equals(value.toString())) { try { value = URLEncoder.encode(value.toString(), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } if (keyToLower) { key = key.toLowerCase(); } } return key + "=" + value; }).collect(Collectors.joining("&")); } }def hello_world(): print 'Hello, world!'//编译版本 4.0.30319.17929 for Microsoft (R) .NET Framework 4.5
using System;using System.Collections.Generic;using System.Linq;using System.Text.RegularExpressions;using Newtonsoft.Json;using System.Security.Cryptography;using System.Net;using System.IO;using System.Text;
namespace YiHuiTong{ public class Signature { public static void Main(string[] args) { string callReportUrl = "https://gateway.yihuitong.top/coll-openapi/call/record/callReport"; string nonce = System.Guid.NewGuid().ToString("N"); string timestamp = time(); string apikey = "123456789"; string secretKey = "1234567890"; string method = "POST"; string path = "/coll-openapi/call/record/callReport"; map.Add("callId","1234"); string message = new HmacStringBuild(SigningHeadersContentType.FORM) .setMethod(method) .setPath(path) .setApikey(apikey) .setTimestamp(timestamp) .setNonce(nonce) .setData(map) .build(); Console.WriteLine(message); string SIGNATURE = CreateToken(message,secretKey); Console.WriteLine(SIGNATURE); Dictionary<string, string> headers = new Dictionary<string, string>(); headers.Add("X-SIGNATURE",SIGNATURE); headers.Add("X-TIMESTAMP",timestamp); headers.Add("X-APIKEY",apikey); headers.Add("X-NONCE",nonce); string returnData = Get(callReportUrl, map,headers); Console.WriteLine(returnData); } public static string CreateToken(string message, string secretKey) { secretKey = secretKey ?? ""; var encoding = new System.Text.UTF8Encoding(); byte[] keyByte = encoding.GetBytes(secretKey); byte[] messageBytes = encoding.GetBytes(message); using (var hmacsha256 = new HMACSHA256(keyByte)) { byte[] hashmessage = hmacsha256.ComputeHash(messageBytes); return Convert.ToBase64String(hashmessage); } } public static string time() { TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); return Convert.ToInt64(ts.TotalSeconds).ToString(); } public static string formatUrlMap(Dictionary<string, Object> paraMap,bool urlEncode) { string para = ""; foreach (KeyValuePair<string, Object> kvp in paraMap) { string key = kvp.Key; string _value = kvp.Value.ToString(); if(urlEncode) { key = System.Web.HttpUtility.UrlEncode(key); if(_value!=null && _value.Length==0){ _value = System.Web.HttpUtility.UrlEncode(_value); } } para += key + "=" + _value + "&"; } return para.Substring(0,para.Length - 1); } public static string Get(string serviceUrl, Dictionary<string, string> parameterData,Dictionary<string, string> headers) { // 构建查询字符串 string queryString = BuildQueryString(parameterData); // 创建完整的 URL string requestUrl = serviceUrl + "?" + queryString; //创建Web访问对象 HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(serviceUrl);
myRequest.Method = "GET"; myRequest.Accept = "application/json"; myRequest.AutomaticDecompression = DecompressionMethods.GZip; myRequest.ContentType = "application/json; charset=UTF-8"; myRequest.MaximumAutomaticRedirections = 1; myRequest.AllowAutoRedirect = true;
myRequest.Headers.Add("X-SIGNATURE", headers["X-SIGNATURE"]); myRequest.Headers.Add("X-TIMESTAMP", headers["X-TIMESTAMP"]); myRequest.Headers.Add("X-APIKEY", headers["X-APIKEY"]); myRequest.Headers.Add("X-NONCE", headers["X-NONCE"]);
//通过Web访问对象获取响应内容 HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse(); //通过响应内容流创建StreamReader对象,因为StreamReader更高级更快 StreamReader reader = new StreamReader(myResponse.GetResponseStream(), Encoding.UTF8); //string returnXml = HttpUtility.UrlDecode(reader.ReadToEnd());//如果有编码问题就用这个方法 string returnData = reader.ReadToEnd();//利用StreamReader就可以从响应内容从头读到尾
reader.Close(); myResponse.Close();
return returnData; } static string BuildQueryString(Dictionary<string, string> parameters) { List<string> queryParts = new List<string>(); foreach (var param in parameters) { // 将键值对进行 URL 编码并加入查询部分 queryParts.Add($"{Uri.EscapeDataString(param.Key)}={Uri.EscapeDataString(param.Value)}"); } // 使用 & 连接查询部分 return string.Join("&", queryParts); } } class HmacStringBuild { private SigningHeadersContentType contentType; private string method; private string path; private string apikey; private string timestamp; private string nonce; private Dictionary<string, System.Object> data; public HmacStringBuild(SigningHeadersContentType contentType){ this.contentType = contentType; } public HmacStringBuild setMethod(string method){ this.method = method; return this; } public HmacStringBuild setPath(string path){ this.path = path; return this; } public HmacStringBuild setApikey(string apikey){ this.apikey = apikey; return this; } public HmacStringBuild setTimestamp(string timestamp){ this.timestamp = timestamp; return this; } public HmacStringBuild setNonce(string nonce){ this.nonce = nonce; return this; } public HmacStringBuild setData(Dictionary<string, System.Object> data){ this.data = data; return this; } public string build(){ String str = this.method + "\n" + this.path + "\n" + this.apikey + "\n" + this.timestamp + "\n" + this.nonce + "\n"; if(string.Equals("GET",this.method)){ str = str + Signature.formatUrlMap(this.data,true) + "\n"; }else if(contentType == SigningHeadersContentType.JSON){ str = str + JsonConvert.SerializeObject(this.data) + "\n"; }else{ str = str + Signature.formatUrlMap(this.data,false) + "\n"; } return str; } } enum SigningHeadersContentType{ JSON,FORM };}<?php
function uuid() { $chars = md5(uniqid(mt_rand(), true)); $uuid = substr ( $chars, 0, 8 ) . '-' . substr ( $chars, 8, 4 ) . '-' . substr ( $chars, 12, 4 ) . '-' . substr ( $chars, 16, 4 ) . '-' . substr ( $chars, 20, 12 ); return $uuid ; } function http_get_data($url, $data_string, $header) { // 如果有数据需要附加在URL后面 if ($data_string) { $url .= '?' . $data_string; } echo json_encode($header); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $header ); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 执行请求并获取响应 $return_content = curl_exec($ch); $return_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 关闭 cURL 资源 curl_close($ch);
return array($return_code, $return_content); } function formatUrlMap($paraMap, $urlEncode = true, $keyToLower = false) { if (empty($paraMap)) { return null; }
// 如果不是有序数组,转化为有序数组 if (!is_array($paraMap) || !isset($paraMap[0])) { $paraMap = array_map('strval', $paraMap); // 强制转换为字符串 ksort($paraMap); // 按键排序 }
$result = [];
foreach ($paraMap as $key => $value) { if ($urlEncode) { $key = urlencode($key); if (!empty($value)) { $value = urlencode($value); } } if ($keyToLower) { $key = strtolower($key); } $result[] = $key . '=' . $value; }
return implode('&', $result);} $callReport = "https://gateway.yihuitong.top/coll-openapi/call/record/callReport"; $nonce = uuid(); $timestamp = time(); $apikey = "123456789"; $secretKey = "1234567890"; $method = "GET"; $path = "/coll-openapi/call/record/callReport"; $data = formatUrlMap(array('callId'=> "1234"),true, false); echo $data; $message = $method . "\n" . $path . "\n" . $apikey . "\n" . $timestamp . "\n" . $nonce . "\n" . $data . "\n";
$SIGNATURE = base64_encode(hash_hmac('sha256', $message, $secretKey,true)); // to base64
$header = array( 'X-SIGNATURE: ' . $SIGNATURE, 'X-TIMESTAMP: ' . $timestamp, 'X-APIKEY: ' . $apikey, 'X-NONCE: ' . $nonce, 'Content-Type: application/json; charset=utf-8' );
list($return_code, $return_content) = http_get_data($callReport, $data, $header);
echo $return_code; echo $return_content;?>