public static function encryptedRequest($url, $params, $method = 'POST')
{
self::init();
$config = self::$config;
// 解析URL获取不带query的路径用于加密
$parsedUrl = parse_url($url);
$urlPath = $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . $parsedUrl['path'];
// 1. 加密请求参数
$encryptionData = self::getRequestParam($urlPath, $params);
$timestamp = $encryptionData['timestamp'];
// 2. 生成签名(根据微信文档,签名时 postdata 使用 req_data 即加密后的 JSON)
$reqData = $encryptionData['reqData']; // 加密后的 JSON 字符串
$signature = self::getSignature($urlPath, $timestamp, $reqData);
// 3. 构建请求体(发送时使用 req_ts + req_data 格式)
$requestBody = json_encode([
'req_ts' => $timestamp,
'req_data' => $reqData,
], JSON_UNESCAPED_SLASHES);
// 4. 构建请求头
$headers = [
'Content-Type: application/json',
'Wechatmp-Appid: ' . $config['appid'],
'Wechatmp-TimeStamp: ' . $timestamp,
'Wechatmp-Signature: ' . $signature,
];
// 5. 发送请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody);
}
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception('请求失败: ' . $error);
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
// 6. 解析响应头和响应体
$headerStr = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
$responseHeaders = self::parseHeaders($headerStr);
$responseData = json_decode($body, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('响应解析失败: ' . $body);
}
// 7. 检查是否为加密响应
if (isset($responseData['iv']) && isset($responseData['data']) && isset($responseData['authtag'])) {
// 验签
$isValid = self::verifySign($urlPath, $responseHeaders, $body);
if (!$isValid) {
throw new Exception('响应签名验证失败');
}
// 解密
$ts = $responseHeaders['Wechatmp-TimeStamp'] ?? time();
return self::decryptToString($urlPath, $ts, $responseData);
}
// 非加密响应直接返回
return $responseData;
}
public static function getRequestParam($url, $req)
{
self::init();
$config = self::$config;
$time = time();
$nonce = str_replace('=', '', base64_encode(random_bytes(16)));
$addReq = ["_n" => $nonce, "_appid" => $config['appid'], "_timestamp" => $time]; // 添加字段
$realReq = array_merge($addReq, $req);
$plaintext = json_encode($realReq);
//额外参数
$message = $url . "|" . $config['appid'] . "|" . $time . "|" . $config['aes_sn'];
$aesKey = base64_decode($config['aes_key']);
$iv = random_bytes(12); // 12位随机字符
$tag = '';
// 数据加密处理
$cipher = openssl_encrypt($plaintext, "aes-256-gcm", $aesKey, OPENSSL_RAW_DATA, $iv, $tag, $message);
if ($cipher === false) {
throw new Exception("Encryption failed: " . openssl_error_string());
}
$iv = base64_encode($iv);
$data = base64_encode($cipher);
$authTag = base64_encode($tag);
$reqData = ["iv" => $iv, "data" => $data, "authtag" => $authTag];
return ['timestamp' => $time, 'reqData' => json_encode($reqData)];
}
public static function getSignature($url, $timestamp, $postData)
{
self::init();
$config = self::$config;
// 签名格式:urlpath\nappid\ntimestamp\npostdata
$payload = $url . "\n" . $config["appid"] . "\n" . $timestamp . "\n" . $postData;
$rsa = RSA::loadPrivateKey($config['private_key']);
// 微信要求:PSS填充方式,salt长度为32(与SHA256结果长度一致)
$rsa = $rsa->withPadding(RSA::SIGNATURE_PSS)
->withHash('sha256')
->withMGFHash('sha256')
->withSaltLength(32);
$signature = $rsa->sign($payload);
return base64_encode($signature);
}
$url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$accessToken}";
$params = [
'scene' => $scene,
'check_path' => false
];
$result = Wechat::encryptedRequest($url, $params);
var_dump($result);
输出:{ ["errcode"]=> int(40234) ["errmsg"]=> string(49) "invalid signature rid: 69787ed6-77fb24e3-01ef1aef" }
