本文目录导读:

在PHP项目中实现用户自定义验签,通常用于API接口安全、支付回调验证、数据完整性校验等场景,下面我给你一个完整的、可复用的实现方案。
核心设计思路
- 签名算法:用户可选择的签名方式(如MD5、SHA256、HMAC等)
- 参数排序:按字母顺序排序参数,保持签名的确定性
- 密钥管理:用户自定义密钥
- 防重放攻击:加入时间戳和随机数(Nonce)
完整实现代码
签名工具类
<?php
namespace App\Utils;
class SignUtil
{
/**
* 生成签名
*
* @param array $params 待签名的参数
* @param string $secretKey 密钥
* @param string $method 签名算法 (md5, sha256, hmac-md5, hmac-sha256)
* @param bool $addTimestamp 是否自动添加时间戳
* @param bool $addNonce 是否自动添加随机数
* @return string
*/
public static function generateSign(
array $params,
string $secretKey,
string $method = 'md5',
bool $addTimestamp = true,
bool $addNonce = true
): string {
// 1. 过滤掉签名字段本身和空值
$params = self::filterParams($params);
// 2. 添加时间戳和随机数(如果需要)
if ($addTimestamp) {
$params['timestamp'] = time();
}
if ($addNonce) {
$params['nonce'] = self::generateNonce();
}
// 3. 按键名字母升序排序
ksort($params);
// 4. 拼接参数
$paramStr = self::buildQueryString($params);
// 5. 根据算法生成签名
return self::signByMethod($paramStr, $secretKey, $method);
}
/**
* 验证签名
*
* @param array $params 包含签名的所有参数
* @param string $secretKey 密钥
* @param string $method 签名算法
* @param int $timeout 时间戳有效时长(秒),0表示不验证
* @return bool
*/
public static function verifySign(
array $params,
string $secretKey,
string $method = 'md5',
int $timeout = 300
): bool {
// 1. 检查签名是否存在
if (!isset($params['sign'])) {
return false;
}
$clientSign = $params['sign'];
// 2. 过滤掉签名字段
$filteredParams = self::filterParams($params);
// 3. 验证时间戳(防重放)
if ($timeout > 0 && isset($filteredParams['timestamp'])) {
$timestamp = intval($filteredParams['timestamp']);
if (abs(time() - $timestamp) > $timeout) {
return false; // 签名超时
}
}
// 4. 直接使用当前参数重新生成签名(不考虑签名算法中的自动添加)
// 这里不能调用 generateSign(),因为会重新添加 timestamp/nonce
// 我们需要手动处理
// 5. 按键名字母升序排序
ksort($filteredParams);
// 6. 拼接参数
$paramStr = self::buildQueryString($filteredParams);
// 7. 计算服务端签名
$serverSign = self::signByMethod($paramStr, $secretKey, $method);
// 8. 比较签名(建议使用安全比较)
return hash_equals($clientSign, $serverSign);
}
/**
* 过滤参数:移除sign字段和空值
*/
private static function filterParams(array $params): array
{
$result = [];
foreach ($params as $key => $value) {
if ($key === 'sign' || $value === '' || $value === null) {
continue;
}
$result[$key] = $value;
}
return $result;
}
/**
* 拼接查询字符串(URL编码)
*/
private static function buildQueryString(array $params): string
{
$parts = [];
foreach ($params as $key => $value) {
// 处理数组参数:a[0]=1&a[1]=2
if (is_array($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
}
$parts[] = $key . '=' . urlencode((string)$value);
}
return implode('&', $parts);
}
/**
* 根据指定算法生成签名
*/
private static function signByMethod(string $paramStr, string $secretKey, string $method): string
{
switch (strtolower($method)) {
case 'sha256':
return hash('sha256', $paramStr . $secretKey);
case 'sha1':
return sha1($paramStr . $secretKey);
case 'hmac-md5':
return hash_hmac('md5', $paramStr, $secretKey);
case 'hmac-sha256':
return hash_hmac('sha256', $paramStr, $secretKey);
case 'md5':
default:
return md5($paramStr . $secretKey);
}
}
/**
* 生成随机数
*/
private static function generateNonce(int $length = 16): string
{
return bin2hex(random_bytes($length / 2));
}
}
用户配置管理(支持自定义验签)
<?php
namespace App\Models;
class UserSignConfig
{
private $userId;
private $secretKey; // 用户自定义密钥
private $signMethod; // 用户自定义签名算法
public function __construct(int $userId, string $secretKey, string $signMethod = 'md5')
{
$this->userId = $userId;
$this->secretKey = $secretKey;
$this->signMethod = $signMethod;
}
/**
* 获取用户的签名配置(可从数据库读取)
*/
public static function getByUserId(int $userId): ?self
{
// 这里应该从数据库查询,示例代码直接返回模拟数据
// $user = DB::table('user_sign_configs')->where('user_id', $userId)->first();
$dbData = [
1 => ['secret_key' => 'abc123', 'sign_method' => 'sha256'],
2 => ['secret_key' => 'xyz789', 'sign_method' => 'hmac-md5'],
];
if (isset($dbData[$userId])) {
return new self(
$userId,
$dbData[$userId]['secret_key'],
$dbData[$userId]['sign_method']
);
}
return null;
}
public function getSecretKey(): string
{
return $this->secretKey;
}
public function getSignMethod(): string
{
return $this->signMethod;
}
}
中间件实现(自动验证签名)
<?php
namespace App\Http\Middleware;
use Closure;
use App\Utils\SignUtil;
use App\Models\UserSignConfig;
class VerifyUserSign
{
public function handle($request, Closure $next)
{
$params = $request->all();
// 获取用户ID(根据你的认证方式)
$userId = $request->input('user_id') ?? $request->header('X-User-Id');
if (!$userId) {
return response()->json(['code' => 400, 'msg' => '缺少用户标识'], 400);
}
// 获取用户的签名配置
$userConfig = UserSignConfig::getByUserId($userId);
if (!$userConfig) {
return response()->json(['code' => 400, 'msg' => '用户验签配置不存在'], 400);
}
// 验证签名
$isValid = SignUtil::verifySign(
$params,
$userConfig->getSecretKey(),
$userConfig->getSignMethod(),
300 // 5分钟超时
);
if (!$isValid) {
return response()->json(['code' => 401, 'msg' => '签名验证失败'], 401);
}
return $next($request);
}
}
客户端调用示例(PHP)
<?php
// 客户端准备请求
$userId = 1;
$secretKey = 'abc123'; // 从用户配置获取
$signMethod = 'sha256';
// 业务参数
$params = [
'user_id' => $userId,
'name' => '张三',
'amount' => 100.50,
'order_id' => 'ORDER20231201',
];
// 生成签名
$sign = SignUtil::generateSign(
$params,
$secretKey,
$signMethod,
true, // 自动加时间戳
true // 自动加随机数
);
// 将签名加入参数
$params['sign'] = $sign;
// 发送请求
$ch = curl_init('https://your-api.com/api/submit');
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
数据库设计(用户自定义配置)
CREATE TABLE `user_sign_configs` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL COMMENT '用户ID',
`secret_key` varchar(64) NOT NULL COMMENT '用户自定义密钥',
`sign_method` varchar(20) NOT NULL DEFAULT 'md5' COMMENT '签名算法(md5,sha256,hmac-md5,hmac-sha256)',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:1启用,0禁用',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户签名配置表';
安全建议
- 密钥管理:密钥要在服务端加密存储,前端只下发一次
- 防重放攻击:一定要验证时间戳,建议超时时间300秒
- 签名比较:使用
hash_equals()防止时序攻击 - 日志记录:记录签名验证失败的日志,方便排查问题
- 限流:配合接口限流,防止暴力破解签名
使用示例
// 1. 生成签名(客户端)
$sign = SignUtil::generateSign(
['a' => 1, 'b' => 2],
'my_secret_key',
'sha256'
);
// 2. 验证签名(服务端)
$result = SignUtil::verifySign(
['a' => 1, 'b' => 2, 'sign' => $sign, 'timestamp' => time()],
'my_secret_key',
'sha256'
);
这套方案灵活、安全、可扩展,用户可以根据自己的安全需求选择不同的签名算法和密钥。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播