getRealMethod(); if (!config('api_crypto.enabled') || $method === 'OPTIONS' || !$request->is( 'api/wallet/*', 'api/newPc', 'api/newPc/*', 'api/issue', 'api/issue/*', 'api/pcissue', 'api/pcissue/*', )) { return $next($request); } $isEncrypted = $request->headers->get(self::HEADER) === '1'; $plaintextUser = $this->isPlaintextUser($request); if ($plaintextUser && !$isEncrypted) { return $this->markPlaintextResponse($next($request)); } try { $crypto = new ApiCryptoService( (string) config('api_crypto.key'), (int) config('api_crypto.timestamp_tolerance', 300), (int) config('api_crypto.max_payload_bytes', 2097152), ); } catch (RuntimeException) { $response = $this->error(5001, 'API 加密配置错误,请联系服务端管理员', 500)->prepare($request); return $plaintextUser ? $this->markPlaintextResponse($response) : $response; } if (!$isEncrypted) { if (config('api_crypto.allow_plaintext')) { return $next($request); } return $this->encryptResponse($this->error(4001, '请求必须使用加密协议', 400), $crypto, $request); } $queryMethod = in_array($method, ['GET', 'HEAD'], true); try { $payload = $queryMethod ? ($request->query->all()['payload'] ?? null) : ($request->isJson() ? $request->json('payload') : null); if (!is_string($payload) || $payload === '') { throw new ApiCryptoException('请求缺少加密 payload'); } $parameters = $crypto->decrypt($payload, ApiCryptoService::DIRECTION_REQUEST); $this->replaceParameters($request, $parameters, $queryMethod); } catch (ApiCryptoException | JsonException $exception) { $response = $this->error(4002, $exception->getMessage(), 400); return $plaintextUser ? $this->markPlaintextResponse($response->prepare($request)) : $this->encryptResponse($response, $crypto, $request); } $response = $next($request); return $plaintextUser ? $this->markPlaintextResponse($response) : $this->encryptResponse($response, $crypto, $request); } private function isPlaintextUser(Request $request): bool { $userIds = config('api_crypto.plaintext_user_ids', []); $token = $request->header('Authorization'); if ($userIds === [] || !is_string($token) || $token === '') { return false; } try { $session = UserSession::findActiveToken($token, now()->getTimestamp()); if (!$session || !hash_equals((string) $session->token, $token) || !in_array((string) $session->user_id, $userIds, true)) { return false; } $user = User::where('user_id', $session->user_id)->first(); } catch (QueryException) { return false; } return $user && (string) $user->user_id === (string) $session->user_id && empty($user->getAttributes()['delete_time']) && (int) ($user->getAttributes()['is_banned'] ?? 0) !== 1; } private function markPlaintextResponse(Response $response): Response { $response->headers->set(self::HEADER, '0'); $response->headers->set('Cache-Control', 'no-store'); return $response; } private function replaceParameters(Request $request, array $parameters, bool $queryMethod): void { $headers = $request->headers->all(); $server = $request->server->all(); $server['REQUEST_URI'] = explode('?', $request->getRequestUri(), 2)[0]; $server['QUERY_STRING'] = ''; // The HTTP verb belongs to the transport, not to the wrapped parameters. unset($parameters['_method'], $headers['x-http-method-override'], $server['HTTP_X_HTTP_METHOD_OVERRIDE']); $headers['accept'] = ['application/json']; $server['HTTP_ACCEPT'] = 'application/json'; $body = $queryMethod ? '' : json_encode((object) $parameters, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); unset($headers['content-length'], $server['CONTENT_LENGTH']); $request->initialize( $queryMethod ? $parameters : [], $queryMethod ? [] : $parameters, $request->attributes->all(), $request->cookies->all(), [], $server, $body, ); $request->headers->replace($headers); $request->setJson($queryMethod ? $request->query : $request->request); } private function encryptResponse(Response $response, ApiCryptoService $crypto, Request $request): Response { $response->headers->set('Cache-Control', 'no-store'); $response->headers->set(self::HEADER, '1'); $response->headers->remove('Content-Length'); $response->headers->remove('ETag'); if ($request->getRealMethod() === 'HEAD' || $response->isEmpty()) { $response->setContent(''); return $response; } try { $data = json_decode((string) $response->getContent(), false, 512, JSON_THROW_ON_ERROR); $envelope = ['encrypted' => true, 'payload' => $crypto->encrypt($data, ApiCryptoService::DIRECTION_RESPONSE)]; if ($response instanceof JsonResponse) { $response->setData($envelope); } else { $response->setContent(json_encode($envelope, JSON_THROW_ON_ERROR)); $response->headers->set('Content-Type', 'application/json'); } return $response; } catch (ApiCryptoException | JsonException) { return $this->error(5002, 'API 响应加密失败', 500); } } private function error(int $code, string $message, int $status): JsonResponse { return new JsonResponse(['code' => $code, 'show' => 1, 'msg' => $message, 'data' => []], $status, [ 'Cache-Control' => 'no-store', ]); } }