本文目录导读:

我来为您详细说明PHP API签名验证的完整流程。
签名验证的基本原理
核心思想
通过特定的算法将请求参数、密钥和时间戳等信息生成签名,服务端验证签名确保请求的合法性和完整性。
标准签名验证流程
客户端签名生成流程
<?php
/**
* 客户端签名生成
*/
class ApiSignClient {
private $appKey;
private $appSecret;
public function __construct($appKey, $appSecret) {
$this->appKey = $appKey;
$this->appSecret = $appSecret;
}
/**
* 生成签名
* @param array $params 业务参数(不包含签名)
* @param string $timestamp 时间戳
* @param string $nonce 随机字符串
* @return string
*/
public function generateSign($params, $timestamp, $nonce) {
// 1. 加入公共参数
$params['app_key'] = $this->appKey;
$params['timestamp'] = $timestamp;
$params['nonce'] = $nonce;
// 2. 按key排序
ksort($params);
// 3. 拼接字符串
$str = urldecode(http_build_query($params));
// 4. 加入密钥并加密
$sign = md5($str . $this->appSecret);
return $sign;
}
/**
* 生成请求参数
*/
public function createRequestParams($businessParams) {
$timestamp = time();
$nonce = $this->generateNonce(16);
$params = array_merge($businessParams, [
'app_key' => $this->appKey,
'timestamp' => $timestamp,
'nonce' => $nonce
]);
$params['sign'] = $this->generateSign($businessParams, $timestamp, $nonce);
return $params;
}
/**
* 生成随机字符串
*/
private function generateNonce($length = 16) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$nonce = '';
for ($i = 0; $i < $length; $i++) {
$nonce .= $chars[rand(0, strlen($chars) - 1)];
}
return $nonce;
}
}
服务端签名验证流程
<?php
/**
* 服务端签名验证
*/
class ApiSignServer {
private $appKey;
private $appSecret;
private $timeout; // 请求超时时间(秒)
public function __construct($appKey, $appSecret, $timeout = 300) {
$this->appKey = $appKey;
$this->appSecret = $appSecret;
$this->timeout = $timeout;
}
/**
* 验证签名主方法
* @param array $params 请求参数
* @return bool|string 成功返回true,失败返回错误信息
*/
public function verifySign($params) {
try {
// 1. 检查必填参数
$this->checkRequiredParams($params);
// 2. 验证AppKey
if ($params['app_key'] !== $this->appKey) {
return 'Invalid app_key';
}
// 3. 验证时间戳
if (!$this->checkTimestamp($params['timestamp'])) {
return 'Timestamp expired';
}
// 4. 验证Nonce(防止重放攻击)
if (!$this->checkNonce($params['nonce'])) {
return 'Nonce repeated';
}
// 5. 重新生成签名并对比
$sign = $this->generateSign($params);
if ($sign !== $params['sign']) {
return 'Sign verification failed';
}
// 6. 记录nonce(需要在红缓存或数据库中实现)
$this->saveNonce($params['nonce'], $this->timeout);
return true;
} catch (Exception $e) {
return $e->getMessage();
}
}
/**
* 检查必填参数
*/
private function checkRequiredParams($params) {
$required = ['app_key', 'timestamp', 'nonce', 'sign'];
foreach ($required as $field) {
if (!isset($params[$field]) || empty($params[$field])) {
throw new Exception("Missing required parameter: {$field}");
}
}
// 检查业务参数是否完整(根据具体业务)
// ...
}
/**
* 验证时间戳是否过期
*/
private function checkTimestamp($timestamp) {
$currentTime = time();
if ($timestamp < $currentTime - $this->timeout || $timestamp > $currentTime + $this->timeout) {
return false;
}
return true;
}
/**
* 检查Nonce是否重复
*/
private function checkNonce($nonce) {
// 实现方式1:Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
if ($redis->exists("api_nonce:{$nonce}")) {
return false;
}
return true;
// 实现方式2:数据库
// $query = "SELECT COUNT(*) FROM api_nonce WHERE nonce = ?";
// if (查询结果 > 0) return false;
// return true;
}
/**
* 保存Nonce(设置过期时间)
*/
private function saveNonce($nonce, $timeout) {
// Redis实现
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->setex("api_nonce:{$nonce}", $timeout, time());
// 数据库实现
// INSERT INTO api_nonce (nonce, created_at) VALUES (?, NOW())
// 定期清理过期记录
}
/**
* 生成服务端签名
*/
private function generateSign($params) {
// 去除签名参数
unset($params['sign']);
// 按key排序
ksort($params);
// 拼接字符串
$str = urldecode(http_build_query($params));
// 加入密钥并加密
return md5($str . $this->appSecret);
}
}
// 使用示例
$apiSignServer = new ApiSignServer('your_app_key', 'your_app_secret');
$verifyResult = $apiSignServer->verifySign($_GET);
if ($verifyResult === true) {
// 签名验证通过,处理业务逻辑
echo json_encode(['code' => 200, 'message' => 'Success']);
} else {
// 签名验证失败
echo json_encode(['code' => 401, 'message' => $verifyResult]);
}
高级签名方案(更安全)
使用HMAC-SHA256签名
<?php
/**
* 高级签名生成(使用HMAC-SHA256)
*/
class AdvancedApiSign {
/**
* 生成签名
*/
public static function generateSign($params, $appSecret) {
// 1. 过滤空值和签名字段
$params = array_filter($params, function($value) {
return $value !== '' && $value !== null && $value !== 'sign';
});
// 2. 按key排序
ksort($params);
// 3. 拼接字符串 "key1=value1&key2=value2"
$str = http_build_query($params);
// 4. 使用HMAC-SHA256加密
$sign = hash_hmac('sha256', $str, $appSecret);
return strtoupper($sign);
}
/**
* 验证签名
*/
public static function verifySign($params, $appSecret) {
if (!isset($params['sign'])) {
return false;
}
$clientSign = $params['sign'];
unset($params['sign']);
$serverSign = self::generateSign($params, $appSecret);
// 使用恒等比较防止时序攻击
return hash_equals($serverSign, $clientSign);
}
}
更安全的签名流程(包含请求体签名)
<?php
/**
* 完整的安全签名方案
*/
class SecureApiSign {
private $appKey;
private $appSecret;
private $timestamp;
private $nonce;
/**
* 完整签名流程
*/
public function signRequest($method, $path, $body = null, $timestamp = null) {
$timestamp = $timestamp ?? time();
$nonce = $this->generateNonce(32);
$params = [
'app_key' => $this->appKey,
'timestamp' => $timestamp,
'nonce' => $nonce,
'method' => strtoupper($method),
'path' => $path
];
// 如果有请求体,加入签名
if ($body !== null) {
$params['body'] = sha1($body);
}
// 生成签名
ksort($params);
$str = urldecode(http_build_query($params));
$sign = hash_hmac('sha256', $str, $this->appSecret);
return [
'params' => $params,
'sign' => $sign
];
}
/**
* 验证请求
*/
public function verifyRequest($request) {
try {
// 1. 验证时间戳(5分钟内有效)
if (abs(time() - $request['timestamp']) > 300) {
throw new Exception('Request expired');
}
// 2. 验证Nonce
if (!$this->verifyNonce($request['nonce'])) {
throw new Exception('Nonce already used');
}
// 3. 验证签名
$signParams = $request;
unset($signParams['sign']);
ksort($signParams);
$str = urldecode(http_build_query($signParams));
$expectedSign = hash_hmac('sha256', $str, $this->appSecret);
if (!hash_equals($expectedSign, $request['sign'])) {
throw new Exception('Invalid signature');
}
// 4. 验证请求体
if (isset($request['body'])) {
$actualBodyHash = sha1(file_get_contents('php://input'));
if ($actualBodyHash !== $request['body']) {
throw new Exception('Body verification failed');
}
}
return true;
} catch (Exception $e) {
error_log("Signature verification failed: " . $e->getMessage());
return false;
}
}
private function generateNonce($length) {
return bin2hex(random_bytes($length / 2));
}
}
中间件实现(Laravel示例)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class VerifyApiSignature
{
public function handle(Request $request, Closure $next)
{
$signature = $request->header('X-Signature');
$timestamp = $request->header('X-Timestamp');
$nonce = $request->header('X-Nonce');
if (!$signature || !$timestamp || !$nonce) {
return response()->json(['error' => 'Missing signature parameters'], 401);
}
// 获取API密钥
$apiKey = $request->header('X-Api-Key');
$appSecret = $this->getSecretByKey($apiKey);
if (!$appSecret) {
return response()->json(['error' => 'Invalid API key'], 401);
}
// 验证时间戳
if (abs(time() - $timestamp) > 300) {
return response()->json(['error' => 'Request expired'], 401);
}
// 验证Nonce(Redis实现)
if (Redis::exists("nonce:{$nonce}")) {
return response()->json(['error' => 'Nonce repeated'], 401);
}
// 重新生成签名
$data = $request->all();
$data['timestamp'] = $timestamp;
$data['nonce'] = $nonce;
ksort($data);
$str = http_build_query($data);
$expectedSignature = hash_hmac('sha256', $str, $appSecret);
if (!hash_equals($expectedSignature, $signature)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
// 保存Nonce
Redis::setex("nonce:{$nonce}", 300, time());
return $next($request);
}
private function getSecretByKey($apiKey)
{
// 从数据库或配置中获取密钥
return 'your_app_secret';
}
}
安全建议
签名算法选择
- 简单应用:MD5(但不够安全)
- 生产环境:HMAC-SHA256
- 高安全性:RSA非对称加密
防攻击措施
- 时间限制:签名有有效期(建议5-10分钟)
- 防重放:使用Nonce,一次请求只能使用一次
- 请求限制:IP限流、用户限流
密钥管理
- 定期更换密钥
- 密钥分级管理(不同级别使用不同密钥)
- 密钥加密存储
日志记录
- 记录所有签名验证失败请求
- 记录异常行为用于安全分析
完整集成示例
<?php
/**
* API请求类(客户端)
*/
class ApiClient {
private $baseUrl;
private $appKey;
private $appSecret;
public function __construct($baseUrl, $appKey, $appSecret) {
$this->baseUrl = $baseUrl;
$this->appKey = $appKey;
$this->appSecret = $appSecret;
}
public function request($method, $path, $params = []) {
$timestamp = time();
$nonce = bin2hex(random_bytes(16));
// 构造签名数据
$signData = array_merge([
'app_key' => $this->appKey,
'timestamp' => $timestamp,
'nonce' => $nonce,
'method' => strtoupper($method),
'path' => $path
], $params);
// 生成签名
ksort($signData);
$str = urldecode(http_build_query($signData));
$sign = hash_hmac('sha256', $str, $this->appSecret);
// 发送请求
$headers = [
'X-Api-Key: ' . $this->appKey,
'X-Timestamp: ' . $timestamp,
'X-Nonce: ' . $nonce,
'X-Signature: ' . $sign,
'Content-Type: application/json'
];
// 使用curl发送请求
$ch = curl_init($this->baseUrl . $path);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if (strtoupper($method) === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
}
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
一个完整的API签名验证流程通常包含:
- 客户端负责生成签名(参数排序、拼接、加密)
- 服务端验证签名(检查时间戳、Nonce、重新生成签名比对)
- 安全防护(防重放攻击、防篡改、密钥管理等)
选择合适的签名算法和实现方案能显著提升API的安全性。