, "t": , "ip"?: }, * encrypted with the openssl key configured in services.client_api.key. Blocks callers * that don't hold the shared key (external scraping/direct API calls); * the timestamp caps replay of a captured header to MAX_SKEW_SECONDS. * * portal-client gọi API từ Next server nên REMOTE_ADDR luôn là IP của Next. IP thật của khách * nằm trong payload đã mã hoá (không giả mạo được như X-Forwarded-For) → gán lại REMOTE_ADDR * để throttle/log dùng đúng IP. Middleware này được ưu tiên chạy trước ThrottleRequests (Kernel). */ private const MAX_SKEW_SECONDS = 300; public function handle(Request $request, Closure $next) { $key = config('services.client_api.key'); $secret = config('services.client_api.secret'); $payload = $key && $secret ? $this->decode($request->header('x-access-token', ''), $key) : null; if (!$payload || !is_string($payload['s'] ?? null) || !hash_equals($secret, $payload['s']) || !is_int($payload['t'] ?? null) || abs(time() - $payload['t']) > self::MAX_SKEW_SECONDS) { return response()->json([ 'error' => true, 'message' => 'Invalid access token', 'code' => 403, ], 403); } if (filter_var($payload['ip'] ?? null, FILTER_VALIDATE_IP)) { $request->server->set('REMOTE_ADDR', $payload['ip']); } return $next($request); } private function decode(string $token, string $key): ?array { $raw = base64_decode($token, true); if ($raw === false || strlen($raw) <= 16) { return null; } $plaintext = openssl_decrypt(substr($raw, 16), 'aes-256-cbc', hex2bin($key), OPENSSL_RAW_DATA, substr($raw, 0, 16)); $payload = $plaintext === false ? null : json_decode($plaintext, true); return is_array($payload) ? $payload : null; } }