公共请求头和签名
创思安全2024-11-07 12:30:52
公共 Header 请求头
提示
公共参数是用于标识用户和接口签名的参数,在平台内调用所有接口都要传入该参数,如非必要,在每个接口单独的文档中不再对这些参数进行说明,但每次请求均需要携带这些参数,才能正常发起请求。
关于公共参数中的Authorization
签名字符串,请参考签名算法。
- 参数说明:
参数名称 必选 类型 说明 X-Timestamp 是 string 当前 UNIX 时间戳,可记录发起 API 请求的时间。例如 1730986454309
。注意:如果与服务器时间相差超过 3 分钟,会引起签名过期错误。X-Nonce 是 string 随机字符串,要求 10-40 位,用来防止接口重放 。例如 a6a01407-f06f-451e-9d37-f9bdc4d7bc11
Content-Type 是 string HTTP 请求内容类型,目前固定为 application/json
Authorization 是 string 认证签名字符串,例如: ak_0f77303296f58fbfa4f158432e8:5062004fc8ad1d842bcc1b7876629e484da19a570729fe7ec8c66183e5444ef5
签名字符串由两部分组成,其中:
前半部分为用户在控制台中AccessKey 管理页面中创建的AccessKey
,后半部分为签名算法生成的签名。
签名算法
提示
以下文档说明了整个签名过程,过程中不涉及任何开发语言,仅为伪代码示例和原理,具体实现请根据实际开发语言进行实现。
为什么要签名
签名通过以下方式帮助保护请求:
- 验证请求者的身份 签名确保请求是由持有有效访问密钥的人发送的。
- 保护传输中的数据 为了防止请求在传输过程中被篡改,创思安全 API 会使用请求参数、时间戳和随机字符串参与签名算法,并将生成的签名作为请求的一部分,发送到 API 服务器。服务器会使用收到的请求参数以同样的过程在此进行签名,并验证请求中签名。如果请求被篡改,将导致签名结果不一致, API 服务器将拒绝本次请求。
申请安全凭证
本文使用的安全凭证为签名密钥,密钥包括 AccessKey 和 SecretKey。
- AccessKey:用于标识 API 调用者身份,可以简单类比为用户名。
- SecretKey:用于验证 API 调用者的身份,可以简单类比为密码。
- 用户必须严格保管安全凭证,避免泄露,否则将危及财产安全。如已泄露,请立刻禁用该安全凭证。
申请安全凭证的具体步骤如下:
- 通过登录控制台 点击右上角头像 。
- 在
AccessKey 管理
页面,单击【新建密钥】创建一对密钥。
如何签名
此过程为伪代码,仅讲解每一步的原理与流程,在实际调用接口时,请根据实际情况来,每个接口的参数并不相同,不要照抄这个例子的参数和值。
拼接待签名字符串规范请求串
按如下伪代码格式拼接规范请求串StringToSign = HTTPRequestMethod + '\n' + CanonicalURI + '\n' + RequestBodyString + '\n' + Timestamp + '\n' + Nonce
- 待签名字符串拼接示例
POST /api/content/safety %7B%22content%22%3A%22test%22%2C%22strategyKey%22%3A%20%22key-123456%22%7D 1731042327221 c3aed234-7856-43b8-9c74-7542020e2ff8
- 待签名字符串参数说明:
字段名称 说明 HTTPRequestMethod HTTP 请求方法(大多数请求都为 POST )。示例: POST
CanonicalURL 业务请求地址 URL,请根据不同的业务进行调整此 URL。示例:文本内容安全 URL /api/verify/signature
RequestBodyString 请求正文(即 body)的 json 字符串,并必须进行 URL 编码
。此示例为{"content":"test","strategyKey":"key-123456"}
,经过URL 编码
为%7B%22content%22%3A%22test%22%2C%22strategyKey%22%3A%20%22key-123456%22%7D
注意:此处的参与签名的 json 字符串必须和请求体中参数一致,并且 json 字符串中不得包含多余的空格,即键与值之间的冒号:
及键值对之间的逗号,
后均不得有空格。严格遵守上述格式要求,可以避免因空格导致的数据传输错误Timestamp 当前 UNIX 时间戳,单位为毫秒。可记录发起 API 请求的时间。例如 1730986454309
。注意:如果与服务器时间相差超过 3 分钟,会引起签名过期错误。Nonce 随机字符串,要求 10-40 位,用来防止接口重放 。例如 a6a01407-f06f-451e-9d37-f9bdc4d7bc11
计算签名
不同开发语言此处的方式可能有所不同,请根据自己常用的开发进行调整
计算签名伪代码如下:Signature = HexEncode(HMAC_SHA256(StringToSign, SecretKey))
- 签名结果示例(仅参考)
5062004fc8ad1d842bcc11771629e484da19a570729fe7ec8c66183e5444ef5
- 签名伪代码说明:
字段名称 说 明 SecretKey 为用户在控制台申请的密钥。此示例取值为 sk_88674930cb078005133704aad205e8967*******
StringToSign 待签名字符串。具体请参考上一步拼接待签名字符 HMACSignature 使用 HMAC-SHA256 算法生成的签名。 HexSignature 将签名转换为十六进制格式的字符串。
拼接 Authorization
按如下格式拼接 Authorization:Authorization = AccessKey + ':' + Signature
字段名称 说 明 AccessKey 为用户在控制台AccessKey 管理中申请 Signature 为上一步中计算得到的签名字符串 - 签名结果示例(仅参考)
ak_0f77303296f58fbfa4f153432e8:5062004fc8ad1d842bcc11771629e484da19a570729fe7ec8c66183e5444ef5
拼接完成后,效果如下(仅参考)
POST /api/content/safety HTTP/1.1 Host: api.chuangsiai.com X-Timestamp: 1731042327221 X-Nonce: c3aed234-7856-43b8-9c74-7542020e2ff8 Content-Type: application/json Authorization: ak_0f77303296f58fbfa4f153432e8:5062004fc8ad1d842bcc11771629e484da19a570729fe7ec8c66183e5444ef5 {"content":"test","strategyKey":"key-123456"}
验证签名是否成功
参考签名验证
警告
请求发送时的 HTTP 头部(Header)和请求体(Body)必须和签名计算过程中的内容完全一致,否则会返回签名不一致错误。可以通过打印实际请求内容,网络抓包等方式对比排查。
当您通过上述步骤完成 签名 后,可通过如下方式验证签名内容是否正确。
请求域名
- 接口域名
https://chuangsi.model.svipc.cn
- 接口请求地址
/api/verify/signature
请求方式 POST
请求头
参考公共请求头部分即可
请求参数
验证签名期间,一般为空对象即可,也可以自行填写自定义参数进行测试签名是否正确 {}
签名验证示例代码
下面代码示例为各种开发语言的签名验证示例,您可以根据自己的开发语言选择对应的示例代码进行验证。如果报错请检查签名过程是否正确。
const CryptoJS = require("crypto-js");
// ak和sk需要替换成自己的ak和sk
const ak = "ak_xxxxxx";
const sk = "sk_xxxxxx";
const host = "https://api.chuangsiai.com";
// 请求方法,必须大写
const method = "POST";
// 请求地址,必须以/开头
const url = "/api/verify/signature";
// 请求参数,需要转换为json字符串
const param = {};
// 请求参数转换为json字符串并进行 URL 编码。注意:待编码的字符串必须为 JSON 格式,并且json中的冒号、逗号前后不能有空格,否则可能导致签名错误。
const paramString = encodeURIComponent(JSON.stringify(param));
// 当前时间戳,单位毫秒
const timestamp = Date.now();
// 随机字符串,可根据业务需求自己生成,这里简单使用md5生成
const nonce = CryptoJS.MD5(Math.random().toString()).toString();
// 拼接待签名的字符串
const stringToSign = `${method}\n${url}\n${paramString}\n${timestamp}\n${nonce}`;
// 使用sk对stringToSign进行HmacSHA256加密
const hmac = CryptoJS.HmacSHA256(stringToSign, sk);
const signature = CryptoJS.enc.Hex.stringify(hmac);
const myHeaders = new Headers();
myHeaders.append("X-Timestamp", timestamp.toString());
myHeaders.append("X-Nonce", nonce);
myHeaders.append("Authorization", `${ak}:${signature}`);
myHeaders.append("Content-Type", "application/json");
const requestOptions = {
method: "POST",
headers: myHeaders,
body: JSON.stringify(param),
};
fetch(`${host}${url}`, requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.log("error", error));
import hashlib
import hmac
import json
import time
import random
import requests
from urllib.parse import quote
# ak、sk 需要替换成自己的
ak = "ak_xxxxxx"
sk = "sk_xxxxxx"
host = "https://api.chuangsiai.com"
# 请求方法,必须大写
method = "POST"
# 请求地址,必须以/开头
url = "/api/verify/signature"
# 请求参数,需要转换为json字符串
param = {}
# 请求参数转换为json字符串并进行 URL 编码 python 中的 json.dumps 默认使用 ascii 编码,需要指定 ensure_ascii=False,并且 separators=(',', ':') 可以去掉 json 字符串中的空格
param_string = quote(json.dumps(param,ensure_ascii=False,separators=(',', ':')))
# 当前时间戳,单位毫秒
timestamp = int(time.time() * 1000)
# 随机字符串
nonce = hashlib.md5(str(random.random()).encode('utf-8')).hexdigest()
# 拼接待签名的字符串
string_to_sign = f"{method}\n{url}\n{param_string}\n{timestamp}\n{nonce}"
# 使用sk对stringToSign进行HmacSHA256加密
signature = hmac.new(sk.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
# 构建请求头
headers = {
"X-Timestamp": str(timestamp),
"X-Nonce": nonce,
"Authorization": f"{ak}:{signature}",
"Content-Type": "application/json"
}
# 请求体
body = json.dumps(param)
# 发送请求
try:
response = requests.post(f"{host}{url}", headers=headers, data=body)
response_data = response.json()
# 处理返回结果
print(response_data)
except requests.exceptions.RequestException as e:
print(f"Error during request: {e}")
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import com.google.gson.Gson;
public class ApiRequest {
public static void main(String[] args) {
// ak、sk 需要替换成自己的
String ak = "ak_xxxxxx";
String sk = "sk_xxxxxx";
String host = "https://api.chuangsiai.com";
// 请求方法,必须大写
String method = "POST";
// 请求地址,必须以/开头
String url = "/api/verify/signature";
// 请求参数,需要转换为json字符串
String param = new Gson().toJson(new RequestParam());
try {
// 请求参数转换为json字符串并进行 URL 编码
String paramString = URLEncoder.encode(param, StandardCharsets.UTF_8.toString());
// 当前时间戳,单位毫秒
long timestamp = System.currentTimeMillis();
// 随机字符串
String nonce = generateRandomString();
// 拼接待签名的字符串
String stringToSign = method + "\n" + url + "\n" + paramString + "\n" + timestamp + "\n" + nonce;
// 使用sk对stringToSign进行HmacSHA256加密
String signature = hmacSha256(stringToSign, sk);
// 构建请求头
HttpURLConnection connection = (HttpURLConnection) new URL(host + url).openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("X-Timestamp", String.valueOf(timestamp));
connection.setRequestProperty("X-Nonce", nonce);
connection.setRequestProperty("Authorization", ak + ":" + signature);
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
// 发送请求体
try (OutputStream os = connection.getOutputStream()) {
byte[] input = param.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// 读取响应
try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
String response;
StringBuilder responseStr = new StringBuilder();
while ((response = br.readLine()) != null) {
responseStr.append(response);
}
System.out.println(responseStr.toString());
}
} catch (Exception e) {
System.out.println("Error during request: " + e.getMessage());
}
}
// 用于生成随机字符串
private static String generateRandomString() {
return String.format("%032x", new java.math.BigInteger(1, java.security.SecureRandom.getInstanceStrong().generateSeed(16)));
}
// HMAC SHA256 签名
private static String hmacSha256(String data, String key) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKeySpec);
byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return bytesToHex(rawHmac);
}
// 将字节数组转为十六进制字符串
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
hexString.append(String.format("%02x", b));
}
return hexString.toString();
}
// 请求参数类
static class RequestParam {
RequestParam() {
}
}
}
package main
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
)
// 请求参数结构体
type RequestParam struct {
}
func main() {
// ak、sk 需要替换成自己的
ak := "ak_xxxxxx"
sk := "sk_xxxxxx"
host := "https://api.chuangsiai.com"
// 请求方法,必须大写
method := "POST"
// 请求地址,必须以/开头
apiUrl := "/api/verify/signature"
// 请求参数
param := RequestParam{ }
// 请求参数转换为json字符串并进行 URL 编码
paramBytes, err := json.Marshal(param)
if err != nil {
fmt.Println("Error marshalling param:", err)
return
}
paramString := url.QueryEscape(string(paramBytes))
// 当前时间戳,单位毫秒
timestamp := time.Now().UnixMilli()
// 随机字符串
nonce := generateRandomString()
// 拼接待签名的字符串
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%d\n%s", method, apiUrl, paramString, timestamp, nonce)
// 使用sk对stringToSign进行HmacSHA256加密
signature := hmacSha256(stringToSign, sk)
// 构建请求头
req, err := http.NewRequest(method, host+apiUrl, bytes.NewBuffer(paramBytes))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", timestamp))
req.Header.Set("X-Nonce", nonce)
req.Header.Set("Authorization", fmt.Sprintf("%s:%s", ak, signature))
req.Header.Set("Content-Type", "application/json")
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
// 读取响应
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response body:", err)
return
}
// 输出响应
fmt.Println(string(body))
}
// 生成随机字符串
func generateRandomString() string {
n := 32
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
fmt.Println("Error generating random string:", err)
return ""
}
return hex.EncodeToString(b)
}
// HMAC SHA256 签名
func hmacSha256(data, key string) string {
h := hmac.New(sha256.New, []byte(key))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
#!/bin/bash
# ak、sk 需要替换成自己的
ak="ak_xxxxxx"
sk="sk_xxxxxx"
host="https://api.chuangsiai.com"
# 请求方法,必须大写
method="POST"
# 请求地址,必须以/开头
url="/api/verify/signature"
# 请求参数,转为 JSON 字符串
param="{}"
# 请求参数转换为 URL 编码
param_string=$(echo -n "$param" | jq -sRr @uri)
# 当前时间戳,单位毫秒
timestamp=$(($(date +%s) * 1000))
# 随机字符串
nonce=$(echo -n $(date +%s%N) | sha256sum | head -c 32)
# 拼接待签名的字符串
string_to_sign="${method}\n${url}\n${param_string}\n${timestamp}\n${nonce}"
# 使用 sk 对 string_to_sign 进行 HMAC SHA256 加密。
# 这里注意 echo -en $string_to_sign ,不要使用 echo -e $string_to_sign ,这样的参数会多一个换行符,导致签名错误
signature=$(echo -en $string_to_sign | openssl dgst -sha256 -hmac "$sk" | sed 's/^.* //')
# 构建请求头
headers=(
-H "X-Timestamp: $timestamp"
-H "X-Nonce: $nonce"
-H "Authorization: $ak:$signature"
-H "Content-Type: application/json"
)
# 发送请求
response=$(curl -X $method "$host$url" "${headers[@]}" -d "$param")
# 输出响应结果
echo "$response"
#include <iostream>
#include <string>
#include <ctime>
#include <random>
#include <sstream>
#include <iomanip>
#include <curl/curl.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <nlohmann/json.hpp>
#include <curl/curl.h>
// 辅助函数:将字节数组转换为十六进制字符串
std::string to_hex(const unsigned char* data, size_t length) {
std::ostringstream hex_stream;
for (size_t i = 0; i < length; ++i) {
hex_stream << std::setw(2) << std::setfill('0') << std::hex << (int)data[i];
}
return hex_stream.str();
}
// 辅助函数:生成一个随机字符串 (nonce)
std::string generate_random_string() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 15); // 生成 0x0 到 0xf 之间的随机数
std::stringstream ss;
for (int i = 0; i < 32; ++i) {
ss << std::hex << dis(gen); // 将每个随机数转为十六进制字符
}
return ss.str();
}
// 辅助函数:使用 HMAC-SHA256 算法进行加密
std::string hmac_sha256(const std::string& data, const std::string& key) {
unsigned char* result;
static unsigned char hmac_result[SHA256_DIGEST_LENGTH];
result = HMAC(EVP_sha256(), key.c_str(), key.length(), (unsigned char*)data.c_str(), data.length(), hmac_result, nullptr);
return to_hex(hmac_result, SHA256_DIGEST_LENGTH);
}
// 发送 API 请求的函数
void send_request(const std::string& ak, const std::string& sk) {
std::string host = "https://api.chuangsiai.com";
// 请求方法,必须大写
std::string method = "POST";
// 请求 URL,必须以 "/" 开头
std::string url = "/api/verify/signature";
// 创建 JSON 请求体
nlohmann::json param = {};
std::string param_string = param.dump(); // 将 JSON 对象序列化为字符串
// 当前时间戳(毫秒)
long long timestamp = std::time(nullptr) * 1000;
// 生成随机字符串(nonce)
std::string nonce = generate_random_string();
// 拼接待签名的字符串
std::string string_to_sign = method + "\n" + url + "\n" + param_string + "\n" + std::to_string(timestamp) + "\n" + nonce;
// 使用 HMAC-SHA256 算法生成签名
std::string signature = hmac_sha256(string_to_sign, sk);
// 准备请求头
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, ("X-Timestamp: " + std::to_string(timestamp)).c_str());
headers = curl_slist_append(headers, ("X-Nonce: " + nonce).c_str());
headers = curl_slist_append(headers, ("Authorization: " + ak + ":" + signature).c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
// 准备 CURL 请求
CURL* curl = curl_easy_init();
if (curl) {
std::string url_full = host + url;
// 设置 CURL 选项
curl_easy_setopt(curl, CURLOPT_URL, url_full.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, param_string.c_str());
// 执行请求
CURLcode res = curl_easy_perform(curl);
// 处理返回结果
if (res != CURLE_OK) {
std::cerr << "CURL 错误: " << curl_easy_strerror(res) << std::endl;
} else {
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
std::cout << "响应码: " << http_code << std::endl;
}
// 清理
curl_easy_cleanup(curl);
curl_slist_free_all(headers);
} else {
std::cerr << "CURL 初始化失败" << std::endl;
}
}
int main() {
// 需要的库:
// OpenSSL:用于进行 HMAC-SHA256 签名。
// nlohmann/json:用于处理 JSON 数据,C++ 中常用的 JSON 库。
// CURL:用于发送 HTTP 请求。
// ak、sk 需要替换成自己的值
std::string ak = "ak_xxxxxx";
std::string sk = "sk_xxxxxx";
// 发送请求
send_request(ak, sk);
return 0;
}