false, 'error' => __('error.internal.secret_not_configured')]; } // 만료 시간 검증 (현재 시간 기준 SIGNATURE_VALID_DURATION 초 이내) $now = time(); if ($exp < $now) { Log::warning('[InternalTokenService] Signature expired', [ 'exp' => $exp, 'now' => $now, 'diff' => $now - $exp, ]); return ['valid' => false, 'error' => __('error.internal.signature_expired')]; } if ($exp > $now + self::SIGNATURE_VALID_DURATION) { Log::warning('[InternalTokenService] Signature exp too far in future', [ 'exp' => $exp, 'now' => $now, 'diff' => $exp - $now, ]); return ['valid' => false, 'error' => __('error.internal.invalid_exp')]; } // HMAC 서명 검증 $payload = "{$userId}:{$tenantId}:{$exp}"; $expectedSignature = hash_hmac('sha256', $payload, $sharedSecret); if (! hash_equals($expectedSignature, $signature)) { Log::warning('[InternalTokenService] Invalid signature', [ 'user_id' => $userId, 'tenant_id' => $tenantId, ]); return ['valid' => false, 'error' => __('error.internal.invalid_signature')]; } return ['valid' => true]; } /** * 사용자 토큰 발급 * * @param int $userId 사용자 ID * @param int $tenantId 테넌트 ID * @return array{access_token: string, token_type: string, expires_in: int}|null */ public function issueToken(int $userId, int $tenantId): ?array { $user = User::find($userId); if (! $user) { Log::warning('[InternalTokenService] User not found', ['user_id' => $userId]); return null; } // 해당 테넌트에 소속되어 있는지 확인 $userTenant = $user->userTenants()->where('tenant_id', $tenantId)->first(); if (! $userTenant) { Log::warning('[InternalTokenService] User not in tenant', [ 'user_id' => $userId, 'tenant_id' => $tenantId, ]); return null; } // 기존 mng_session 토큰 삭제 (중복 방지) $user->tokens()->where('name', 'mng_session')->delete(); // 새 토큰 발급 $token = $user->createToken('mng_session', ['*'], now()->addSeconds(self::TOKEN_EXPIRES_IN)); Log::info('[InternalTokenService] Token issued', [ 'user_id' => $userId, 'tenant_id' => $tenantId, 'token_id' => $token->accessToken->id, ]); return [ 'access_token' => $token->plainTextToken, 'token_type' => 'Bearer', 'expires_in' => self::TOKEN_EXPIRES_IN, ]; } /** * 토큰 교환 실행 (검증 + 발급) * * @param int $userId 사용자 ID * @param int $tenantId 테넌트 ID * @param int $exp 만료 타임스탬프 * @param string $signature HMAC 서명 * @return array{success: bool, data?: array, error?: string} */ public function exchange(int $userId, int $tenantId, int $exp, string $signature): array { // 1. 서명 검증 $verification = $this->verifySignature($userId, $tenantId, $exp, $signature); if (! $verification['valid']) { return [ 'success' => false, 'error' => $verification['error'], ]; } // 2. 토큰 발급 $tokenData = $this->issueToken($userId, $tenantId); if (! $tokenData) { return [ 'success' => false, 'error' => __('error.internal.token_issue_failed'), ]; } return [ 'success' => true, 'data' => $tokenData, ]; } }