- ApiTokenService: API 서버 토큰 교환 서비스 추가 - AuthService: 로그인 성공 시 API 토큰 교환 연동 - 레이아웃: 세션 토큰을 localStorage에 동기화 (FCM 사용) - config/services.php: exchange_secret 설정 추가 환경변수 필요: INTERNAL_EXCHANGE_SECRET (API와 동일)
142 lines
4.2 KiB
PHP
142 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* API 서버 토큰 교환 서비스
|
|
*
|
|
* MNG → API 서버간 HMAC 인증을 통해 Bearer 토큰을 발급받습니다.
|
|
*/
|
|
class ApiTokenService
|
|
{
|
|
/**
|
|
* 서명 유효 시간 (초) - API 서버와 동일하게 5분
|
|
*/
|
|
private const SIGNATURE_VALID_DURATION = 300;
|
|
|
|
/**
|
|
* API 토큰 교환
|
|
*
|
|
* @param int $userId 사용자 ID
|
|
* @param int $tenantId 테넌트 ID
|
|
* @return array{success: bool, data?: array, error?: string}
|
|
*/
|
|
public function exchangeToken(int $userId, int $tenantId): array
|
|
{
|
|
$baseUrl = config('services.api.base_url');
|
|
$exchangeSecret = config('services.api.exchange_secret');
|
|
|
|
if (empty($baseUrl)) {
|
|
Log::error('[ApiTokenService] API base URL not configured');
|
|
|
|
return ['success' => false, 'error' => 'API 서버 URL이 설정되지 않았습니다.'];
|
|
}
|
|
|
|
if (empty($exchangeSecret)) {
|
|
Log::error('[ApiTokenService] Exchange secret not configured');
|
|
|
|
return ['success' => false, 'error' => '토큰 교환 비밀키가 설정되지 않았습니다.'];
|
|
}
|
|
|
|
// 만료 시간 계산 (현재 시간 + 유효 시간)
|
|
$exp = time() + self::SIGNATURE_VALID_DURATION;
|
|
|
|
// HMAC 서명 생성
|
|
$payload = "{$userId}:{$tenantId}:{$exp}";
|
|
$signature = hash_hmac('sha256', $payload, $exchangeSecret);
|
|
|
|
try {
|
|
$response = Http::timeout(10)
|
|
->post("{$baseUrl}/api/v1/internal/exchange-token", [
|
|
'user_id' => $userId,
|
|
'tenant_id' => $tenantId,
|
|
'exp' => $exp,
|
|
'signature' => $signature,
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
$data = $response->json('data');
|
|
Log::info('[ApiTokenService] Token exchanged successfully', [
|
|
'user_id' => $userId,
|
|
'tenant_id' => $tenantId,
|
|
]);
|
|
|
|
return [
|
|
'success' => true,
|
|
'data' => [
|
|
'access_token' => $data['access_token'],
|
|
'token_type' => $data['token_type'] ?? 'Bearer',
|
|
'expires_in' => $data['expires_in'] ?? 3600,
|
|
],
|
|
];
|
|
}
|
|
|
|
$error = $response->json('message') ?? '토큰 교환에 실패했습니다.';
|
|
Log::warning('[ApiTokenService] Token exchange failed', [
|
|
'user_id' => $userId,
|
|
'tenant_id' => $tenantId,
|
|
'status' => $response->status(),
|
|
'error' => $error,
|
|
]);
|
|
|
|
return ['success' => false, 'error' => $error];
|
|
} catch (\Exception $e) {
|
|
Log::error('[ApiTokenService] Token exchange exception', [
|
|
'user_id' => $userId,
|
|
'tenant_id' => $tenantId,
|
|
'exception' => $e->getMessage(),
|
|
]);
|
|
|
|
return ['success' => false, 'error' => 'API 서버 연결에 실패했습니다.'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 세션에 저장된 API 토큰 조회
|
|
*/
|
|
public function getSessionToken(): ?string
|
|
{
|
|
return session('api_access_token');
|
|
}
|
|
|
|
/**
|
|
* API 토큰을 세션에 저장
|
|
*
|
|
* @param string $token Bearer 토큰
|
|
* @param int $expiresIn 만료 시간 (초)
|
|
*/
|
|
public function storeTokenInSession(string $token, int $expiresIn): void
|
|
{
|
|
session([
|
|
'api_access_token' => $token,
|
|
'api_token_expires_at' => now()->addSeconds($expiresIn)->timestamp,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 세션의 API 토큰 삭제
|
|
*/
|
|
public function clearSessionToken(): void
|
|
{
|
|
session()->forget(['api_access_token', 'api_token_expires_at']);
|
|
}
|
|
|
|
/**
|
|
* 토큰이 만료되었는지 확인
|
|
*/
|
|
public function isTokenExpired(): bool
|
|
{
|
|
$expiresAt = session('api_token_expires_at');
|
|
|
|
if (! $expiresAt) {
|
|
return true;
|
|
}
|
|
|
|
// 5분 전에 미리 갱신하도록 (버퍼)
|
|
return time() > ($expiresAt - 300);
|
|
}
|
|
}
|