Appearance
接入前必读
一、API调用基础说明
1. 网关地址
https://openapi.xyiot.link2. 请求头参数
| 参数名 | 是否必填 | 说明 |
|---|---|---|
| Content-Type | 是 | application/json;charset=UTF-8 |
| x-open-client-id | 是 | 星寓开放平台 clientId |
| x-open-auth-mode | 是 | 固定为 SIGNATURE_V3 |
| x-timestamp | 是 | 毫秒级时间戳 |
| x-open-nonce | 是 | 随机 uuid |
| x-open-content-md5 | POST 必填,GET 不传 | 请求体 MD5(Base64编码) |
| x-open-signature | 是 | 请求签名值(基于HmacSHA256加密计算) |
3. 响应顶层参数
| 参数名 | 说明 |
|---|---|
| code | 默认 200 为成功,其他状态码参见「状态码说明」 |
| data | API响应的业务数据 |
| message | 状态码非200情况下返回的异常描述(建议优先使用code做异常识别) |
| requestId | 开放平台返回的当次请求的id,可用于部分异步回调场景匹配回调通知 |
示例:
json
{
"code": "200",
"data": {},
"requestId": "0KLRm9DVcJjTVE3O"
}二、访问鉴权说明
当前采用请求签名方式进行访问鉴权,具体操作步骤如下:
1.签名流程
txt
1.POST 请求:对按参数名字典序排序后的请求体(body)进行 MD5 摘要运算,即得到 contentMD5
2.组装签名字符串:
String signPlaintext = 接口URI + "\n" + contentMD5 + "\n" + timestamp(毫秒时间戳) + "\n" + nonce(随机uuid);
3.对组装好的签名字符串进行 HmacSHA256 算法签名运算,即得到最终签名值
其他说明:
1.当接口为 GET 请求时,URI 需要拼接按参数名字典序生成的 queryString,contentMD5 使用空字符串,且不传 x-open-content-md5 请求头
2.签名、MD5、HTTP body 均使用 UTF-8 编码
3.签名采用 HmacSHA256 算法,签名所使用的密钥为 clientId 匹配的 clientSecret2.请求体(body) MD5摘要运算示例
注意:请求参数(body参数)须按照参数名的字典顺序排序后再进行MD5摘要计算
java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;
// 请求体参数先按参数名字典序排序,再序列化为紧凑 JSON。
// 注意:contentMD5 必须基于实际发送的 bodyJson 计算,不能签名一份 body、发送另一份 body。
Map<String, String> params = new TreeMap<>();
params.put("sn", "123");
String bodyJson = toSortedJson(params);
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] md5Bytes = md5.digest(bodyJson.getBytes(StandardCharsets.UTF_8));
String contentMD5 = Base64.getEncoder().encodeToString(md5Bytes);
static String toSortedJson(Map<String, String> params) {
Map<String, String> sorted = new TreeMap<>(params);
StringBuilder builder = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, String> entry : sorted.entrySet()) {
if (!first) {
builder.append(',');
}
builder.append('"').append(entry.getKey()).append("\":\"")
.append(entry.getValue()).append('"');
first = false;
}
return builder.append('}').toString();
}上述得到的 contentMD5 即为请求头 x-open-content-md5 的值
3.HmacSHA256 签名运算示例
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
String uri = "/openapi/v2/lock/get_lock_info";
String contentMD5 = "上一步计算得到的contentMD5";
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = "随机uuid";
String secret = "your clientSecret";
String signPlaintext = uri + "\n" + contentMD5 + "\n" + timestamp + "\n" + nonce;
String algorithm = "HmacSHA256";
Mac hmacSha256 = Mac.getInstance(algorithm);
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
byte[] signPlaintextBytes = signPlaintext.getBytes(StandardCharsets.UTF_8);
hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, algorithm));
byte[] digestBytes = hmacSha256.doFinal(signPlaintextBytes);
String signature = Base64.getEncoder().encodeToString(digestBytes);上述得到的 signature 即为最终签名值,也即为请求头 x-open-signature 的值
4.API签名完整示例
java
package com.xyiot.test;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
public class SignTestV3 {
private static final String SIGNATURE_PLAINTEXT_FMT = "%s\n%s\n%s\n%s";
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
/** 替换为实际的 API 网关地址 */
private static final String API_GATEWAY = "https://openapi.xyiot.link";
/** 替换为实际的 clientId / clientSecret */
private static final String CLIENT_ID = "your clientId";
private static final String CLIENT_SECRET = "your clientSecret";
public static void main(String[] args) throws Exception {
testPostSignature();
testGetSignature();
}
// ================================================================
// POST 签名测试 (SIGNATURE_V3)
// ================================================================
static void testPostSignature() throws Exception {
System.out.println("========== POST 签名测试 (SIGNATURE_V3) ==========");
var timestamp = System.currentTimeMillis();
var nonce = fastSimpleUUID();
var data = new TreeMap<String, String>();
data.put("sn", "123");
var bodyJson = sortedJson(data);
// contentMd5 = Base64(MD5(排序后的 JSON))
var contentMd5 = md5Base64(bodyJson);
var uri = "/openapi/v2/lock/get_lock_info";
// 签名原文: uri + contentMd5 + timestamp + nonce
var plaintext = String.format(SIGNATURE_PLAINTEXT_FMT, uri, contentMd5, timestamp, nonce);
System.out.println("签名原文:\n" + plaintext);
var signatureVal = hmacSha256Base64(CLIENT_SECRET, plaintext);
var request = HttpRequest.newBuilder(URI.create(API_GATEWAY + uri))
.header("Content-Type", "application/json;charset=UTF-8")
.header("x-open-auth-mode", "SIGNATURE_V3")
.header("x-open-client-id", CLIENT_ID)
.header("x-open-nonce", nonce)
.header("x-timestamp", String.valueOf(timestamp))
.header("x-open-content-md5", contentMd5)
.header("x-open-signature", signatureVal)
.POST(HttpRequest.BodyPublishers.ofString(bodyJson, StandardCharsets.UTF_8))
.build();
var resp = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
System.out.println("响应: " + resp.body());
System.out.println();
}
// ================================================================
// GET 签名: 无 contentMd5, queryString 拼接到 uri 再签名
// ================================================================
static void testGetSignature() throws Exception {
System.out.println("========== GET 签名测试 (SIGNATURE_V3, 无 content-md5) ==========");
var timestamp = System.currentTimeMillis();
var nonce = fastSimpleUUID();
var uri = "/openapi/v2/lock/list_page";
// GET 的 queryString 参与签名: 参数按参数名字典顺序拼接
var queryLine = queryString(Map.of("sn", "ABC123", "page", "1", "size", "10"));
var signedUri = uri + queryLine; // GET 时将 queryString 拼入签名原文
// GET 请求 contentMd5 为空字符串
var contentMd5 = "";
// 签名原文: signedUri + "" + timestamp + nonce
var plaintext = String.format(SIGNATURE_PLAINTEXT_FMT, signedUri, contentMd5, timestamp, nonce);
System.out.println("签名原文:\n" + plaintext);
var signatureVal = hmacSha256Base64(CLIENT_SECRET, plaintext);
// GET 请求不传 x-open-content-md5
var request = HttpRequest.newBuilder(URI.create(API_GATEWAY + signedUri))
.header("x-open-auth-mode", "SIGNATURE_V3")
.header("x-open-client-id", CLIENT_ID)
.header("x-open-nonce", nonce)
.header("x-timestamp", String.valueOf(timestamp))
.header("x-open-signature", signatureVal)
.GET()
.build();
var resp = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
System.out.println("响应: " + resp.body());
System.out.println();
}
static String sortedJson(Map<String, String> data) {
var sorted = new TreeMap<>(data);
var builder = new StringBuilder("{");
var first = true;
for (var entry : sorted.entrySet()) {
if (!first) {
builder.append(',');
}
builder.append('"').append(jsonEscape(entry.getKey())).append("\":\"")
.append(jsonEscape(entry.getValue())).append('"');
first = false;
}
return builder.append('}').toString();
}
static String queryString(Map<String, String> params) {
if (params.isEmpty()) {
return "";
}
var sorted = new TreeMap<>(params);
var builder = new StringBuilder("?");
var first = true;
for (var entry : sorted.entrySet()) {
if (!first) {
builder.append('&');
}
builder.append(urlEncode(entry.getKey())).append('=').append(urlEncode(entry.getValue()));
first = false;
}
return builder.toString();
}
static String md5Base64(String text) throws Exception {
var md5 = MessageDigest.getInstance("MD5");
return java.util.Base64.getEncoder()
.encodeToString(md5.digest(text.getBytes(StandardCharsets.UTF_8)));
}
static String hmacSha256Base64(String secret, String text) throws Exception {
var mac = Mac.getInstance("HmacSHA256");
var key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(key);
return java.util.Base64.getEncoder()
.encodeToString(mac.doFinal(text.getBytes(StandardCharsets.UTF_8)));
}
static String fastSimpleUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
private static String jsonEscape(String text) {
var builder = new StringBuilder();
for (var i = 0; i < text.length(); i++) {
var ch = text.charAt(i);
switch (ch) {
case '"' -> builder.append("\\\"");
case '\\' -> builder.append("\\\\");
case '\b' -> builder.append("\\b");
case '\f' -> builder.append("\\f");
case '\n' -> builder.append("\\n");
case '\r' -> builder.append("\\r");
case '\t' -> builder.append("\\t");
default -> {
if (ch < 0x20) {
builder.append(String.format("\\u%04x", (int) ch));
} else {
builder.append(ch);
}
}
}
}
return builder.toString();
}
private static String urlEncode(String text) {
return URLEncoder.encode(text, StandardCharsets.UTF_8);
}
}go
package main
import (
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
// ============================================================
// 配置 (替换为实际值)
// ============================================================
const (
API_GATEWAY = "https://openapi.xyiot.link"
CLIENT_ID = "your client_id"
CLIENT_SECRET = "your secret"
SIGNATURE_PLAINTEXT_FMT = "%s\n%s\n%s\n%s"
)
// 对应 IdUtil.fastSimpleUUID(): 无横线的 UUID v4 (32 位 hex)
func fastSimpleUUID() string {
b := make([]byte, 16)
rand.Read(b)
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
return fmt.Sprintf("%x", b)
}
// key 排序且紧凑输出
func sortJSON(data map[string]string) string {
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
var sb strings.Builder
sb.WriteByte('{')
for i, k := range keys {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(fmt.Sprintf("%q:%q", k, data[k]))
}
sb.WriteByte('}')
return sb.String()
}
func queryString(params map[string]string) string {
if len(params) == 0 {
return ""
}
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
var sb strings.Builder
sb.WriteByte('?')
for i, k := range keys {
if i > 0 {
sb.WriteByte('&')
}
sb.WriteString(url.QueryEscape(k))
sb.WriteByte('=')
sb.WriteString(url.QueryEscape(params[k]))
}
return sb.String()
}
// 对应 Base64.encodeBase64(SecureUtil.md5().digest(...))
func md5Base64(text string) string {
h := md5.Sum([]byte(text))
return base64.StdEncoding.EncodeToString(h[:])
}
// 对应 Base64.encodeBase64(SecureUtil.hmac(HmacSHA256, key).digest(...))
func hmacSha256Base64(key, text string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(text))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
// 发起 HTTP 请求并输出响应
func doRequest(method, url string, headers map[string]string, body string) {
client := &http.Client{Timeout: 30 * time.Second}
var reqBody io.Reader
if body != "" {
reqBody = strings.NewReader(body)
}
req, err := http.NewRequest(method, url, reqBody)
if err != nil {
fmt.Printf("请求构造失败: %v\n\n", err)
return
}
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("请求失败: %v\n\n", err)
return
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Printf("HTTP %d\n%s\n\n", resp.StatusCode, string(respBody))
}
// ============================================================
// POST 签名测试 (SIGNATURE_V3)
// ============================================================
func testPostSignature() {
fmt.Println("========== POST 签名测试 (SIGNATURE_V3) ==========")
timestamp := time.Now().UnixMilli()
nonce := fastSimpleUUID()
data := map[string]string{"sn": "abc123"}
bodyJSON := sortJSON(data)
// 签名计算使用 key 排序后的 JSON
contentMD5 := md5Base64(bodyJSON)
uri := "/openapi/v2/lock/get_lock_info"
plaintext := fmt.Sprintf(SIGNATURE_PLAINTEXT_FMT, uri, contentMD5, timestamp, nonce)
fmt.Printf("body: %s\n", bodyJSON)
fmt.Printf("content-md5: %s\n", contentMD5)
fmt.Printf("签名原文:\n%s\n\n", plaintext)
signature := hmacSha256Base64(CLIENT_SECRET, plaintext)
doRequest("POST", API_GATEWAY+uri, map[string]string{
"Content-Type": "application/json;charset=UTF-8",
"x-open-auth-mode": "SIGNATURE_V3",
"x-open-client-id": CLIENT_ID,
"x-open-nonce": nonce,
"x-timestamp": fmt.Sprintf("%d", timestamp),
"x-open-content-md5": contentMD5,
"x-open-signature": signature,
}, bodyJSON)
}
// ============================================================
// GET 签名: 无 contentMd5, queryString 拼接到 uri 再签名
// ============================================================
func testGetSignature() {
fmt.Println("========== GET 签名测试 (SIGNATURE_V3, 无 content-md5) ==========")
timestamp := time.Now().UnixMilli()
nonce := fastSimpleUUID()
uri := "/openapi/v2/lock/list_page"
queryLine := queryString(map[string]string{"sn": "ABC123", "page": "1", "size": "10"})
signedURI := uri + queryLine
contentMD5 := ""
plaintext := fmt.Sprintf(SIGNATURE_PLAINTEXT_FMT, signedURI, contentMD5, timestamp, nonce)
fmt.Printf("signed_uri: %s\n", signedURI)
fmt.Printf("签名原文:\n%s\n\n", plaintext)
signature := hmacSha256Base64(CLIENT_SECRET, plaintext)
doRequest("GET", API_GATEWAY+signedURI, map[string]string{
"x-open-auth-mode": "SIGNATURE_V3",
"x-open-client-id": CLIENT_ID,
"x-open-nonce": nonce,
"x-timestamp": fmt.Sprintf("%d", timestamp),
"x-open-signature": signature,
}, "")
}
func main() {
testPostSignature()
testGetSignature()
}py
#!/usr/bin/env python3
import base64
import hashlib
import hmac
import json
import time
import urllib.parse
import uuid
import requests
# ============================================================
# 配置 (替换为实际值)
# ============================================================
API_GATEWAY = "https://openapi.xyiot.link"
CLIENT_ID = "your client_id"
CLIENT_SECRET = "your secret"
SIGNATURE_PLAINTEXT_FMT = "{}\n{}\n{}\n{}"
def fast_simple_uuid() -> str:
"""对应 IdUtil.fastSimpleUUID(): 无横线的 UUID (32 位 hex)"""
return uuid.uuid4().hex
def sort_json(data: dict) -> str:
"""key 排序且紧凑输出"""
return json.dumps(data, sort_keys=True, separators=(",", ":"))
def query_string(params: dict) -> str:
"""按参数名字典顺序生成 queryString"""
if not params:
return ""
return "?" + urllib.parse.urlencode(sorted(params.items()))
def md5_base64(text: str) -> str:
"""对应 Base64.encodeBase64(SecureUtil.md5().digest(...))"""
return base64.b64encode(hashlib.md5(text.encode("utf-8")).digest()).decode()
def hmac_sha256_base64(key: str, text: str) -> str:
"""对应 Base64.encodeBase64(SecureUtil.hmac(HmacSHA256, key).digest(...))"""
return base64.b64encode(
hmac.new(key.encode("utf-8"), text.encode("utf-8"), hashlib.sha256).digest()
).decode()
# ============================================================
# POST 签名测试 (SIGNATURE_V3)
# ============================================================
def test_post_signature():
print("========== POST 签名测试 (SIGNATURE_V3) ==========")
timestamp = int(time.time() * 1000)
nonce = fast_simple_uuid()
data = {"sn": "42B30119"}
body_json = sort_json(data)
content_md5 = md5_base64(body_json)
uri = "/openapi/v2/lock/get_lock_info"
# 签名原文: uri + contentMd5 + timestamp + nonce
plaintext = SIGNATURE_PLAINTEXT_FMT.format(uri, content_md5, timestamp, nonce)
print(f"签名原文:\n{plaintext}")
signature = hmac_sha256_base64(CLIENT_SECRET, plaintext)
resp = requests.post(
API_GATEWAY + uri,
headers={
"Content-Type": "application/json;charset=UTF-8",
"x-open-auth-mode": "SIGNATURE_V3",
"x-open-client-id": CLIENT_ID,
"x-open-nonce": nonce,
"x-timestamp": str(timestamp),
"x-open-content-md5": content_md5,
"x-open-signature": signature,
},
data=body_json.encode("utf-8"),
)
print(f"响应: {resp.text}")
print()
# ============================================================
# GET 签名: 无 contentMd5, queryString 拼接到 uri 再签名
# ============================================================
def test_get_signature():
print("========== GET 签名测试 (SIGNATURE_V3, 无 content-md5) ==========")
timestamp = int(time.time() * 1000)
nonce = fast_simple_uuid()
uri = "/openapi/v2/lock/list_page"
query_line = query_string({"sn": "ABC123", "page": "1", "size": "10"})
signed_uri = uri + query_line # GET 时将 queryString 拼入签名原文
content_md5 = "" # GET 请求 contentMd5 为空字符串
plaintext = SIGNATURE_PLAINTEXT_FMT.format(signed_uri, content_md5, timestamp, nonce)
print(f"签名原文:\n{plaintext}")
signature = hmac_sha256_base64(CLIENT_SECRET, plaintext)
# GET 请求不传 x-open-content-md5
resp = requests.get(
API_GATEWAY + signed_uri,
headers={
"x-open-auth-mode": "SIGNATURE_V3",
"x-open-client-id": CLIENT_ID,
"x-open-nonce": nonce,
"x-timestamp": str(timestamp),
"x-open-signature": signature,
},
)
print(f"响应: {resp.text}")
print()
if __name__ == "__main__":
test_post_signature()
test_get_signature()php
#!/usr/bin/env php
<?php
// ============================================================
// 配置 (替换为实际值)
// ============================================================
define('API_GATEWAY', 'https://openapi.xyiot.link');
define('CLIENT_ID', 'your client_id');
define('CLIENT_SECRET', 'your secret');
const SIGNATURE_PLAINTEXT_FMT = "%s\n%s\n%s\n%s";
/**
* 对应 IdUtil.fastSimpleUUID(): 无横线的 UUID v4 (32 位 hex)
*/
function fastSimpleUUID(): string
{
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return bin2hex($data);
}
/**
* key 排序且紧凑输出
*/
function sortJson(array $data): string
{
ksort($data);
return json_encode($data, JSON_UNESCAPED_UNICODE);
}
/**
* 按参数名字典顺序生成 queryString
*/
function queryString(array $params): string
{
if (empty($params)) {
return '';
}
ksort($params);
return '?' . http_build_query($params);
}
/**
* 对应 Base64.encodeBase64(SecureUtil.md5().digest(...))
*/
function md5Base64(string $text): string
{
return base64_encode(md5($text, true));
}
/**
* 对应 Base64.encodeBase64(SecureUtil.hmac(HmacSHA256, key).digest(...))
*/
function hmacSha256Base64(string $key, string $text): string
{
return base64_encode(hash_hmac('sha256', $text, $key, true));
}
/**
* 发起 HTTP 请求并输出响应
*/
function doRequest(string $method, string $url, array $headers, ?string $body = null): void
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => array_map(fn($k, $v) => "$k: $v", array_keys($headers), $headers),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
echo "HTTP $httpCode\n";
echo $response ?: $error;
echo "\n\n";
}
// ============================================================
// POST 签名测试 (SIGNATURE_V3)
// ============================================================
function testPostSignature(): void
{
echo "========== POST 签名测试 (SIGNATURE_V3) ==========\n";
$timestamp = intval(microtime(true) * 1000);
$nonce = fastSimpleUUID();
$data = ["sn" => "abc123"];
$bodyJson = sortJson($data);
$contentMd5 = md5Base64($bodyJson);
$uri = "/openapi/v2/lock/get_lock_info";
$plaintext = sprintf(SIGNATURE_PLAINTEXT_FMT, $uri, $contentMd5, $timestamp, $nonce);
echo "body: $bodyJson\n";
echo "content-md5: $contentMd5\n";
echo "签名原文:\n$plaintext\n\n";
$signature = hmacSha256Base64(CLIENT_SECRET, $plaintext);
doRequest('POST', API_GATEWAY . $uri, [
'Content-Type' => 'application/json;charset=UTF-8',
'x-open-auth-mode' => 'SIGNATURE_V3',
'x-open-client-id' => CLIENT_ID,
'x-open-nonce' => $nonce,
'x-timestamp' => (string) $timestamp,
'x-open-content-md5' => $contentMd5,
'x-open-signature' => $signature,
], $bodyJson);
}
// ============================================================
// GET 签名: 无 contentMd5, queryString 拼接到 uri 再签名
// ============================================================
function testGetSignature(): void
{
echo "========== GET 签名测试 (SIGNATURE_V3, 无 content-md5) ==========\n";
$timestamp = intval(microtime(true) * 1000);
$nonce = fastSimpleUUID();
$uri = "/openapi/v2/lock/list_page";
$queryLine = queryString(["sn" => "ABC123", "page" => "1", "size" => "10"]);
$signedUri = $uri . $queryLine;
$contentMd5 = "";
$plaintext = sprintf(SIGNATURE_PLAINTEXT_FMT, $signedUri, $contentMd5, $timestamp, $nonce);
echo "signed_uri: $signedUri\n";
echo "签名原文:\n$plaintext\n\n";
$signature = hmacSha256Base64(CLIENT_SECRET, $plaintext);
doRequest('GET', API_GATEWAY . $signedUri, [
'x-open-auth-mode' => 'SIGNATURE_V3',
'x-open-client-id' => CLIENT_ID,
'x-open-nonce' => $nonce,
'x-timestamp' => (string) $timestamp,
'x-open-signature' => $signature,
]);
}
// ---- main ----
testPostSignature();
testGetSignature();