Procházet zdrojové kódy

中间件-修改验签(参数)中间件

sunxbiao před 1 rokem
rodič
revize
b78183ecdd
1 změnil soubory, kde provedl 28 přidání a 69 odebrání
  1. 28 69
      app/common/middleware/SignatureMiddleware.php

+ 28 - 69
app/common/middleware/SignatureMiddleware.php

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