Appearance
应用签名验证对接文档
文档说明
本文档为统一认证系统对外接口的公共签名验证机制 ,适用于所有需要签名验证的对外接口。客户端在调用任何对外接口前,都需要按照本文档说明构造签名并在请求头中携带。
概述
为了增强接口安全性,统一认证系统支持基于时间戳的 HMAC-SHA256 签名验证机制。客户端需要在每个请求的请求头中携带签名信息,服务端会验证签名的有效性和时间戳的时效性。
重要提示:
- 本文档适用于所有对外接口
- 所有示例代码均使用 HMAC-SHA256 算法,请勿使用简单的 Hash 算法(如 SHA256)
- 签名:
- 老应用Key(AK开头)在调用接口时,签名验证是可选的;如果提供了签名信息,系统会进行验证;如果未提供签名信息,系统不会进行验证(向后兼容)
- 新应用Key(无AK开头)在调用接口时,强制进行签名验证
签名验证规则
1. 请求头参数
所有对外接口请求都需要在请求头中携带以下参数:
| Header | 字段说明 | 类型 | 必填 | 备注 |
|---|---|---|---|---|
| X-App-Key | 应用Key | String | 是 | 统一认证平台分配的应用Key |
| X-Timestamp | 时间戳(毫秒) | String | 是 | Unix时间戳,单位:毫秒 |
| X-App-Signature | 签名 | String | 是 | 基于HMAC-SHA256计算的签名值 |
2. 签名生成规则
签名计算公式:
plain
data = appKey + timestamp
signature = HMAC-SHA256(data, appSecret).toLowerCase()详细说明:
appKey:应用Key(字符串)timestamp:当前时间戳(毫秒,字符串格式)appSecret:应用密钥(字符串,作为 HMAC 的密钥)- 将
appKey和timestamp按顺序拼接作为消息数据 - 使用
appSecret作为密钥,对消息数据进行 HMAC-SHA256 加密 - 将加密结果转换为小写十六进制字符串
⚠️ ** 重要:必须使用 HMAC-SHA256 算法,不能使用简单的 SHA256 或其他 Hash 算法**
3. 时间戳有效期
- 时间戳有效期:5分钟
- 服务端会校验时间戳与当前时间的差值,超过5分钟的请求将被拒绝
- 建议客户端在每次请求时生成新的时间戳
4. 验证流程
- 客户端生成当前时间戳(毫秒)
- 使用 appKey、timestamp、appSecret 通过 HMAC-SHA256 计算签名
- 在请求头中携带 X-App-Key、X-Timestamp、X-App-Signature
- 服务端验证:
- 检查请求头参数完整性
- 验证时间戳是否在有效期内(5分钟)
- 根据 appKey 查询 appSecret
- 使用相同算法(HMAC-SHA256)计算签名并对比
- 验证通过则放行,否则返回错误
客户端签名生成示例代码
以下提供了 Java、Python、PHP、Go 四种语言的完整示例代码,所有示例均使用 HMAC-SHA256 算法 。
Java 示例
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class AppSignatureUtil {
/**
* 生成应用签名(使用 HMAC-SHA256)
*
* @param appKey 应用Key
* @param timestamp 时间戳(毫秒)
* @param appSecret 应用密钥
* @return 签名字符串(小写十六进制)
*/
public static String generateSignature(String appKey, String timestamp, String appSecret) {
try {
// 1. 准备数据:拼接公开参数(不包含 Secret)
String data = appKey + timestamp;
// 2. 使用 HMAC-SHA256 进行加密
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
appSecret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
hmac.init(secretKey);
byte[] hashBytes = hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 3. 转换为十六进制字符串(小写)
StringBuilder result = new StringBuilder();
for (byte b : hashBytes) {
result.append(String.format("%02x", b));
}
return result.toString();
} catch (Exception e) {
throw new RuntimeException("HMAC-SHA256算法不可用", e);
}
}
/**
* 生成带签名的请求头
*
* @param appKey 应用Key
* @param appSecret 应用密钥
* @return 请求头Map
*/
public static Map<String, String> generateHeaders(String appKey, String appSecret) {
long timestamp = System.currentTimeMillis();
String timestampStr = String.valueOf(timestamp);
String signature = generateSignature(appKey, timestampStr, appSecret);
Map<String, String> headers = new HashMap<>();
headers.put("X-App-Key", appKey);
headers.put("X-Timestamp", timestampStr);
headers.put("X-App-Signature", signature);
return headers;
}
// 使用示例
public static void main(String[] args) {
String appKey = "test_app_key_123";
String appSecret = "test_app_secret_456";
Map<String, String> headers = generateHeaders(appKey, appSecret);
System.out.println("请求头:");
headers.forEach((key, value) -> System.out.println(key + ": " + value));
// 使用示例:发送HTTP请求
// HttpClient client = HttpClient.newHttpClient();
// HttpRequest request = HttpRequest.newBuilder()
// .uri(URI.create("https://api.example.com/your-endpoint"))
// .headers(headers.entrySet().stream()
// .map(e -> new String[]{e.getKey(), e.getValue()})
// .flatMap(Arrays::stream)
// .toArray(String[]::new))
// .POST(HttpRequest.BodyPublishers.ofString("{\"key\":\"value\"}"))
// .build();
// HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
}
}Python 示例
python
import hmac
import hashlib
import time
class AppSignatureUtil:
"""应用签名工具类(使用 HMAC-SHA256)"""
@staticmethod
def generate_signature(app_key, timestamp, app_secret):
"""
生成应用签名(使用 HMAC-SHA256)
Args:
app_key: 应用Key
timestamp: 时间戳(毫秒,字符串格式)
app_secret: 应用密钥
Returns:
签名字符串(小写十六进制)
"""
# 1. 准备数据:拼接公开参数(不包含 Secret)
data = app_key + str(timestamp)
# 2. 使用 HMAC-SHA256 进行加密
signature = hmac.new(
app_secret.encode('utf-8'),
data.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
@staticmethod
def generate_headers(app_key, app_secret):
"""
生成带签名的请求头
Args:
app_key: 应用Key
app_secret: 应用密钥
Returns:
请求头字典
"""
timestamp = int(time.time() * 1000) # 毫秒时间戳
timestamp_str = str(timestamp)
signature = AppSignatureUtil.generate_signature(app_key, timestamp_str, app_secret)
headers = {
'X-App-Key': app_key,
'X-Timestamp': timestamp_str,
'X-App-Signature': signature
}
return headers
# 使用示例
if __name__ == '__main__':
app_key = 'test_app_key_123'
app_secret = 'test_app_secret_456'
headers = AppSignatureUtil.generate_headers(app_key, app_secret)
print('请求头:')
for key, value in headers.items():
print(f'{key}: {value}')
# 使用 requests 库发送请求示例
# import requests
# url = 'https://api.example.com/your-endpoint'
# response = requests.post(url, headers=headers, json={'key': 'value'})
# print(response.json())PHP 示例
php
<?php
class AppSignatureUtil
{
/**
* 生成应用签名(使用 HMAC-SHA256)
*
* @param string $appKey 应用Key
* @param string $timestamp 时间戳(毫秒)
* @param string $appSecret 应用密钥
* @return string 签名字符串(小写十六进制)
*/
public static function generateSignature($appKey, $timestamp, $appSecret)
{
// 1. 准备数据:拼接公开参数(不包含 Secret)
$data = $appKey . $timestamp;
// 2. 使用 HMAC-SHA256 进行加密
$signature = hash_hmac('sha256', $data, $appSecret, false);
return $signature;
}
/**
* 生成带签名的请求头
*
* @param string $appKey 应用Key
* @param string $appSecret 应用密钥
* @return array 请求头数组
*/
public static function generateHeaders($appKey, $appSecret)
{
// 获取当前时间戳(毫秒)
$timestamp = (string)(int)(microtime(true) * 1000);
// 计算签名
$signature = self::generateSignature($appKey, $timestamp, $appSecret);
// 构建请求头
$headers = [
'X-App-Key: ' . $appKey,
'X-Timestamp: ' . $timestamp,
'X-App-Signature: ' . $signature
];
return $headers;
}
}
// 使用示例
$appKey = 'test_app_key_123';
$appSecret = 'test_app_secret_456';
$headers = AppSignatureUtil::generateHeaders($appKey, $appSecret);
echo "请求头:\n";
foreach ($headers as $header) {
echo $header . "\n";
}
// 使用 cURL 发送请求示例
// $ch = curl_init();
// curl_setopt($ch, CURLOPT_URL, 'https://api.example.com/your-endpoint');
// curl_setopt($ch, CURLOPT_POST, true);
// curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['key' => 'value']));
// curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($headers, ['Content-Type: application/json']));
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// $response = curl_exec($ch);
// curl_close($ch);
// echo $response;
?>Go 示例
go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"time"
)
// AppSignatureUtil 应用签名工具类
type AppSignatureUtil struct{}
// GenerateSignature 生成应用签名(使用 HMAC-SHA256)
// appKey: 应用Key
// timestamp: 时间戳(毫秒,字符串格式)
// appSecret: 应用密钥
// 返回: 签名字符串(小写十六进制)
func (util *AppSignatureUtil) GenerateSignature(appKey, timestamp, appSecret string) string {
// 1. 准备数据:拼接公开参数(不包含 Secret)
data := appKey + timestamp
// 2. 使用 HMAC-SHA256 进行加密
mac := hmac.New(sha256.New, []byte(appSecret))
mac.Write([]byte(data))
hashBytes := mac.Sum(nil)
// 3. 转换为十六进制字符串(小写)
signature := hex.EncodeToString(hashBytes)
return signature
}
// GenerateHeaders 生成带签名的请求头
// appKey: 应用Key
// appSecret: 应用密钥
// 返回: 请求头Map
func (util *AppSignatureUtil) GenerateHeaders(appKey, appSecret string) map[string]string {
// 获取当前时间戳(毫秒)
timestamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
// 计算签名
signature := util.GenerateSignature(appKey, timestamp, appSecret)
// 构建请求头
headers := map[string]string{
"X-App-Key": appKey,
"X-Timestamp": timestamp,
"X-App-Signature": signature,
}
return headers
}
// 使用示例
func main() {
appKey := "test_app_key_123"
appSecret := "test_app_secret_456"
util := &AppSignatureUtil{}
headers := util.GenerateHeaders(appKey, appSecret)
fmt.Println("请求头:")
for key, value := range headers {
fmt.Printf("%s: %s\n", key, value)
}
// 使用 net/http 发送请求示例
// import (
// "bytes"
// "encoding/json"
// "net/http"
// )
//
// data := map[string]string{"key": "value"}
// jsonData, _ := json.Marshal(data)
//
// req, _ := http.NewRequest("POST", "https://api.example.com/your-endpoint", bytes.NewBuffer(jsonData))
// for key, value := range headers {
// req.Header.Set(key, value)
// }
// req.Header.Set("Content-Type", "application/json")
//
// client := &http.Client{}
// resp, _ := client.Do(req)
// defer resp.Body.Close()
}完整请求示例
Java 完整请求示例
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class ApiRequestExample {
public static void main(String[] args) throws Exception {
String appKey = "your_app_key";
String appSecret = "your_app_secret";
String apiUrl = "https://api.example.com/your-endpoint";
// 1. 生成签名请求头
Map<String, String> headers = AppSignatureUtil.generateHeaders(appKey, appSecret);
// 2. 构建HTTP请求
HttpClient client = HttpClient.newHttpClient();
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.POST(HttpRequest.BodyPublishers.ofString("{\"key\":\"value\"}"));
// 3. 添加请求头
for (Map.Entry<String, String> entry : headers.entrySet()) {
requestBuilder.header(entry.getKey(), entry.getValue());
}
requestBuilder.header("Content-Type", "application/json");
// 4. 发送请求
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("响应状态码: " + response.statusCode());
System.out.println("响应内容: " + response.body());
}
}Python 完整请求示例
python
import requests
from app_signature_util import AppSignatureUtil
app_key = "your_app_key"
app_secret = "your_app_secret"
api_url = "https://api.example.com/your-endpoint"
# 1. 生成签名请求头
headers = AppSignatureUtil.generate_headers(app_key, app_secret)
headers['Content-Type'] = 'application/json'
# 2. 发送请求
response = requests.post(
api_url,
headers=headers,
json={'key': 'value'}
)
print(f"响应状态码: {response.status_code}")
print(f"响应内容: {response.json()}")错误码说明
| 错误码 | 说明 | 解决方案 |
|---|---|---|
| ERR_APP_INVALID_SECRET | 签名验证失败 | 检查签名计算是否正确,时间戳是否在有效期内,确认使用 HMAC-SHA256 算法 |
| ERR_APP_NOT_EXIST | 应用不存在 | 检查 appKey 是否正确 |
| ERR_APP_DISABLE | 应用已禁用 | 联系管理员启用应用 |
| ERR_APP_INVALID_KEY | appKey 不能为空 | 确保请求头中包含 X-App-Key |
注意事项
- 算法要求 :必须使用 HMAC-SHA256 算法 ,不能使用简单的 SHA256 或其他 Hash 算法
- 时间戳格式 :必须使用毫秒级时间戳(Unix时间戳 × 1000)
- 签名大小写 :签名结果必须是小写十六进制字符串
- 字符串拼接顺序 :严格按照
appKey + timestamp的顺序拼接(appSecret 作为 HMAC 的密钥使用,不参与字符串拼接) - 时间戳有效期 :时间戳必须在5分钟内,建议每次请求都生成新的时间戳
- 向后兼容 :如果请求头中不包含签名相关字段,系统不会进行签名验证(向后兼容)
- 安全性建议 :
- 不要在客户端代码中硬编码 appSecret
- 使用环境变量或配置文件存储敏感信息
- 定期更换 appSecret
- 使用 HTTPS 协议传输请求
测试示例
测试参数
plain
appKey = "test_app_key_123"
appSecret = "test_app_secret_456"
timestamp = "1703234567890"签名计算过程
plain
1. 准备数据(拼接公开参数):
data = "test_app_key_123" + "1703234567890"
data = "test_app_key_1231703234567890"
2. HMAC-SHA256加密:
signature = HMAC-SHA256(data, "test_app_secret_456")
3. 转换为十六进制(小写):
signature = hex(signature).lower()完整请求示例(cURL)
bash
# 设置变量
APP_KEY="test_app_key_123"
APP_SECRET="test_app_secret_456"
TIMESTAMP=$(date +%s%3N) # 毫秒时间戳
# 计算签名(使用 HMAC-SHA256,需要安装 openssl)
DATA="${APP_KEY}${TIMESTAMP}"
SIGNATURE=$(echo -n "$DATA" | openssl dgst -sha256 -hmac "$APP_SECRET" | sed 's/^.* //')
# 发送请求
curl -X POST "https://api.example.com/your-endpoint" \
-H "X-App-Key: ${APP_KEY}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-App-Signature: ${SIGNATURE}" \
-H "Content-Type: application/json" \
-d '{"key": "value"}'常见问题
Q1: 签名验证失败,提示"签名不匹配"
A: 请检查以下几点:
- 确认使用的是 HMAC-SHA256 算法,而不是简单的 SHA256 或其他 Hash 算法
- 确认数据拼接顺序:
appKey + timestamp(不包含 appSecret) - 确认 appSecret 作为 HMAC 的密钥使用
- 确认时间戳是否为毫秒级
- 确认签名结果是否为小写十六进制
- 确认 appSecret 是否正确
Q2: 提示"时间戳已过期"
A: 时间戳必须在5分钟内有效,请确保:
- 使用当前时间生成时间戳
- 系统时间同步正确
- 网络延迟在可接受范围内
Q3: 如何判断是否需要签名验证?
A: 如果请求头中包含 X-App-Signature 或 X-Timestamp,系统会进行签名验证。如果都不包含,则不会验证(向后兼容)。
Q4: 签名验证是否影响原有接口?
A:
- 老应用Key(AK开头)在调用接口时,签名验证是可选的;如果提供了签名信息,系统会进行验证;如果未提供签名信息,系统不会进行验证(向后兼容)
- 新应用Key(无AK开头)在调用接口时,强制进行签名验证
Q5: 为什么必须使用 HMAC-SHA256,不能使用 SHA256?
A: HMAC-SHA256 是消息认证码算法,使用密钥对消息进行签名,比简单的 SHA256 更安全。SHA256 只是单向哈希函数,无法提供消息认证功能。
更新日志
| 版本 | 日期 | 说明 |
|---|---|---|
| 1.0 | 2025/12/23 | 初始版本,支持基于HMAC-SHA256的签名验证 |
