route()?->uri() ?? $request->path(); // 화이트리스트 패턴 매칭 (와일드카드 지원) $isPublicRoute = false; foreach ($publicRoutes as $pattern) { if ($pattern === $currentRoute || fnmatch($pattern, $currentRoute)) { $isPublicRoute = true; break; } } // 공개 라우트가 아닌 경우에만 요청 정보 로깅 if (! $isPublicRoute) { Log::info('API Request', [ 'ip' => $request->ip(), 'user_id' => optional($request->user())->id, 'method' => $request->method(), 'uri' => $request->getRequestUri(), 'input' => $request->except(['password', 'password_confirmation']), // 민감 정보 제외 'user_agent' => $request->userAgent(), 'headers' => [ 'X-API-KEY' => $request->header('X-API-KEY') ? '***' : null, 'Authorization' => $request->bearerToken() ? 'Bearer ***' : null, 'Accept' => $request->header('Accept'), 'Content-Type' => $request->header('Content-Type'), ], ]); } // 공개 라우트는 API Key 검증 스킵 if ($isPublicRoute) { return $next($request); } // API Key 검증 $apiKey = $request->header('X-API-KEY'); $validApiKey = false; if ($apiKey) { $validApiKey = DB::table('api_keys') ->where('key', $apiKey) ->where('is_active', true) ->exists(); } if (! $validApiKey) { // 보안 로그 기록 (API Key 없이 접근 시도) Log::warning('Unauthorized API access attempt', [ 'ip' => $request->ip(), 'uri' => $request->getRequestUri(), 'method' => $request->method(), 'user_agent' => $request->userAgent(), ]); return response()->json(['message' => 'Unauthorized. Invalid or missing API key'], 401); } // Bearer 인증 (Sanctum) $user = []; if ($token = $request->bearerToken()) { $accessToken = PersonalAccessToken::findToken($token); if ($accessToken && $accessToken->tokenable instanceof User) { $user = $accessToken->tokenable; if ($user) { // 기본 테넌트(여러개 소속시 활성화 테넌트) $tenantId = $user->userTenants ->where('is_default', 1) ->first()?->tenant_id ?? $user->userTenants->first()?->tenant_id; $tenantId = $tenantId ?? 0; // tenant_id 설정 $request->attributes->set('tenant_id', $tenantId); app()->instance('tenant_id', $tenantId); // user_id 설정 $request->attributes->set('api_user', $user->id); app()->instance('api_user', $user->id); } } } // 화이트리스트(인증 예외 라우트) - Bearer 토큰 없이 접근 가능 $allowWithoutAuth = [ 'api/v1/login', 'api/v1/signup', 'api/v1/register', 'api/v1/refresh', 'api/v1/debug-apikey', 'api/v1/internal/exchange-token', // 내부 서버간 토큰 교환 (HMAC 인증 사용) // 추가적으로 허용하고 싶은 라우트 ]; // 현재 라우트 확인 (경로 또는 이름) $currentRoute = $request->route()?->uri() ?? $request->path(); if (! in_array($currentRoute, $allowWithoutAuth)) { // 인증정보(api_user, tenant_id) 없으면 튕김 if (! app()->bound('api_user')) { throw new AuthenticationException('회원정보 정보 없음'); } } $response = $next($request); // 응답 정보 저장 Log::info('API Response', [ 'uri' => $request->getRequestUri(), 'status' => $response->getStatusCode(), 'content' => $response->getContent(), // 응답 body도 원하면! ]); return $response; } }