SignatureMiddleware.php 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. <?php
  2. namespace app\common\middleware;
  3. use think\facade\Cache;
  4. use think\facade\Log;
  5. use think\Request;
  6. use think\Response;
  7. class SignatureMiddleware
  8. {
  9. public function handle(Request $request, \Closure $next)
  10. {
  11. // 获取必要参数
  12. $essential = $request->only(['timestamp', 'nonce', 'signature']);
  13. // 验证必要参数
  14. if (count(array_filter($essential)) !== 3) {
  15. return $this->errorResponse('参数不完整', 400);
  16. }
  17. // 验证时间戳(5分钟内有效)
  18. if (abs(time() - (int)$essential['timestamp']) > 300) {
  19. return $this->errorResponse('请求已过期', 400);
  20. }
  21. // 验证nonce唯一性
  22. $nonceKey = 'nonce:' . $essential['nonce'];
  23. if (Cache::has($nonceKey)) {
  24. return $this->errorResponse('重复请求', 400);
  25. }
  26. // 构造签名数据(与前端完全一致)
  27. $signData = $request->param();
  28. unset($signData['signature']);
  29. // 按键名排序
  30. ksort($signData);
  31. // 生成签名字符串(与前端相同格式)
  32. $signContent = '';
  33. foreach ($signData as $key => $value) {
  34. $signContent .= "{$key}={$value}&";
  35. }
  36. $signContent = rtrim($signContent, '&');
  37. // 记录原始签名内容(用于调试)
  38. Log::debug("Sign Content: " . $signContent);
  39. // 获取公钥
  40. $publicKey = openssl_pkey_get_public(
  41. file_get_contents(env('RSA_PUBLIC_KEY_PATH'))
  42. );
  43. if (!$publicKey) {
  44. Log::error("公钥加载失败");
  45. return $this->errorResponse('系统错误', 500);
  46. }
  47. // 计算签名的MD5值(与前端一致)
  48. $md5Hash = md5($signContent);
  49. Log::debug("MD5 Hash: " . $md5Hash);
  50. // 解码前端签名(前端使用公钥加密)
  51. $signature = base64_decode($essential['signature']);
  52. // 使用公钥解密签名
  53. $decrypted = '';
  54. $success = openssl_public_decrypt($signature, $decrypted, $publicKey);
  55. if (!$success) {
  56. Log::error("签名解密失败: " . openssl_error_string());
  57. return $this->errorResponse('签名验证失败', 403);
  58. }
  59. Log::debug("解密结果: " . $decrypted);
  60. // 比较解密后的值与MD5哈希
  61. if ($decrypted !== $md5Hash) {
  62. Log::error("签名验证失败: 期望 {$md5Hash}, 实际 {$decrypted}");
  63. return $this->errorResponse('签名验证失败', 403);
  64. }
  65. // 记录已使用的nonce(5分钟过期)
  66. Cache::set($nonceKey, 1, 300);
  67. return $next($request);
  68. }
  69. private function errorResponse(string $message, int $code): Response
  70. {
  71. return json([
  72. 'code' => $code,
  73. 'msg' => $message,
  74. 'data' => null
  75. ])->code($code);
  76. }
  77. }