Skip to content

应用签名验证对接文档

文档说明

本文档为统一认证系统对外接口的公共签名验证机制 ,适用于所有需要签名验证的对外接口。客户端在调用任何对外接口前,都需要按照本文档说明构造签名并在请求头中携带。

概述

为了增强接口安全性,统一认证系统支持基于时间戳的 HMAC-SHA256 签名验证机制。客户端需要在每个请求的请求头中携带签名信息,服务端会验证签名的有效性和时间戳的时效性。

重要提示:

  • 本文档适用于所有对外接口
  • 所有示例代码均使用 HMAC-SHA256 算法,请勿使用简单的 Hash 算法(如 SHA256)
  • 签名:
    • 老应用Key(AK开头)在调用接口时,签名验证是可选的;如果提供了签名信息,系统会进行验证;如果未提供签名信息,系统不会进行验证(向后兼容)
    • 新应用Key(无AK开头)在调用接口时,强制进行签名验证

签名验证规则

1. 请求头参数

所有对外接口请求都需要在请求头中携带以下参数:

Header字段说明类型必填备注
X-App-Key应用KeyString统一认证平台分配的应用Key
X-Timestamp时间戳(毫秒)StringUnix时间戳,单位:毫秒
X-App-Signature签名String基于HMAC-SHA256计算的签名值

2. 签名生成规则

签名计算公式:

plain
data = appKey + timestamp
signature = HMAC-SHA256(data, appSecret).toLowerCase()

详细说明:

  • appKey:应用Key(字符串)
  • timestamp:当前时间戳(毫秒,字符串格式)
  • appSecret:应用密钥(字符串,作为 HMAC 的密钥)
  • appKeytimestamp 按顺序拼接作为消息数据
  • 使用 appSecret 作为密钥,对消息数据进行 HMAC-SHA256 加密
  • 将加密结果转换为小写十六进制字符串

⚠️ ** 重要:必须使用 HMAC-SHA256 算法,不能使用简单的 SHA256 或其他 Hash 算法**

3. 时间戳有效期

  • 时间戳有效期:5分钟
  • 服务端会校验时间戳与当前时间的差值,超过5分钟的请求将被拒绝
  • 建议客户端在每次请求时生成新的时间戳

4. 验证流程

  1. 客户端生成当前时间戳(毫秒)
  2. 使用 appKey、timestamp、appSecret 通过 HMAC-SHA256 计算签名
  3. 在请求头中携带 X-App-Key、X-Timestamp、X-App-Signature
  4. 服务端验证:
    • 检查请求头参数完整性
    • 验证时间戳是否在有效期内(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_KEYappKey 不能为空确保请求头中包含 X-App-Key

注意事项

  1. 算法要求必须使用 HMAC-SHA256 算法 ,不能使用简单的 SHA256 或其他 Hash 算法
  2. 时间戳格式 :必须使用毫秒级时间戳(Unix时间戳 × 1000)
  3. 签名大小写 :签名结果必须是小写十六进制字符串
  4. 字符串拼接顺序 :严格按照 appKey + timestamp 的顺序拼接(appSecret 作为 HMAC 的密钥使用,不参与字符串拼接)
  5. 时间戳有效期 :时间戳必须在5分钟内,建议每次请求都生成新的时间戳
  6. 向后兼容 :如果请求头中不包含签名相关字段,系统不会进行签名验证(向后兼容)
  7. 安全性建议
    • 不要在客户端代码中硬编码 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-SignatureX-Timestamp,系统会进行签名验证。如果都不包含,则不会验证(向后兼容)。

Q4: 签名验证是否影响原有接口?

A:

  • 老应用Key(AK开头)在调用接口时,签名验证是可选的;如果提供了签名信息,系统会进行验证;如果未提供签名信息,系统不会进行验证(向后兼容)
  • 新应用Key(无AK开头)在调用接口时,强制进行签名验证

Q5: 为什么必须使用 HMAC-SHA256,不能使用 SHA256?

A: HMAC-SHA256 是消息认证码算法,使用密钥对消息进行签名,比简单的 SHA256 更安全。SHA256 只是单向哈希函数,无法提供消息认证功能。

更新日志

版本日期说明
1.02025/12/23初始版本,支持基于HMAC-SHA256的签名验证