From 9336cac8c192328467b49f3ec08951935aa7fa3c Mon Sep 17 00:00:00 2001 From: bcjang Date: Wed, 3 Dec 2025 16:05:40 +0900 Subject: [PATCH] =?UTF-8?q?token=EA=B4=80=EB=A6=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/VibePage.tsx | 11 +- src/services/spotifyService.ts | 198 +++++++++------------------------ src/types/api.ts | 18 ++- 3 files changed, 81 insertions(+), 146 deletions(-) diff --git a/src/pages/VibePage.tsx b/src/pages/VibePage.tsx index 055cf5a..30a3308 100644 --- a/src/pages/VibePage.tsx +++ b/src/pages/VibePage.tsx @@ -277,7 +277,16 @@ export function VibePage() { const authResponse = await spotifyService.login(); if (authResponse) { - console.log(authResponse); + console.log("OAuth 응답:", authResponse); + + // 백엔드로 토큰 전송하여 세션 생성 + const callbackResponse = await spotifyService.sendAuthCallback(authResponse); + + if (!callbackResponse.success) { + Alert.error(callbackResponse.error || "세션 생성에 실패했습니다."); + return; + } + Alert.success("Spotify에 로그인되었습니다!"); // 백엔드 API를 통해 플레이리스트 목록 가져오기 diff --git a/src/services/spotifyService.ts b/src/services/spotifyService.ts index 93c1fa2..86f7145 100644 --- a/src/services/spotifyService.ts +++ b/src/services/spotifyService.ts @@ -5,16 +5,17 @@ import type { SpotifyBackendPlaylistsResponse, SpotifyAddTracksRequest, SpotifyAddTracksResponse, + SpotifyAuthCallbackRequest, + SpotifyAuthCallbackResponse, ApiResponse, } from '../types/api'; /** - * Spotify OAuth 2.0 서비스 + * Spotify OAuth 2.0 서비스 (Session 기반) */ class SpotifyService { - private tokenKey = 'spotify_access_token'; - private refreshTokenKey = 'spotify_refresh_token'; - private expiresAtKey = 'spotify_expires_at'; + private sessionIdKey = 'spotify_session_id'; + private sessionExpiresAtKey = 'spotify_session_expires_at'; // Spotify OAuth 설정 private clientId = import.meta.env.VITE_SPOTIFY_CLIENT_ID || ''; @@ -165,13 +166,6 @@ class SpotifyService { const data: SpotifyAuthResponse = await response.json(); - // Token 저장 - this.setToken(data.access_token); - if (data.refresh_token) { - this.setRefreshToken(data.refresh_token); - } - this.setExpiresAt(Date.now() + data.expires_in * 1000); - // 세션 스토리지 정리 sessionStorage.removeItem('spotify_code_verifier'); sessionStorage.removeItem('spotify_state'); @@ -180,96 +174,55 @@ class SpotifyService { } /** - * 사용자 플레이리스트 가져오기 + * OAuth 토큰을 백엔드로 전송하여 세션 생성 */ - async getUserPlaylists(limit: number = 50, offset: number = 0): Promise { - const token = this.getToken(); + async sendAuthCallback(authData: SpotifyAuthResponse): Promise> { + const request: SpotifyAuthCallbackRequest = { + access_token: authData.access_token, + token_type: authData.token_type, + expires_in: authData.expires_in, + refresh_token: authData.refresh_token, + scope: authData.scope, + }; - if (!token) { - throw new Error('로그인이 필요합니다.'); + const response = await apiClient.post( + '/v2/spotify/auth/callback', + request + ); + + if (response.success && response.data) { + this.setSessionId(response.data.session_id); + this.setSessionExpiresAt(Date.now() + response.data.expires_in * 1000); } - const url = new URL('https://api.spotify.com/v1/me/playlists'); - url.searchParams.append('limit', limit.toString()); - url.searchParams.append('offset', offset.toString()); - - const response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - if (!response.ok) { - if (response.status === 401) { - // Token 만료 시 갱신 시도 - await this.refreshAccessToken(); - return this.getUserPlaylists(limit, offset); - } - throw new Error('플레이리스트를 가져오는데 실패했습니다.'); - } - - return await response.json(); - } - - /** - * Access token 갱신 - */ - private async refreshAccessToken(): Promise { - const refreshToken = this.getRefreshToken(); - - if (!refreshToken) { - throw new Error('다시 로그인해주세요.'); - } - - const body = new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: refreshToken, - client_id: this.clientId, - }); - - const response = await fetch('https://accounts.spotify.com/api/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: body.toString(), - }); - - if (!response.ok) { - this.logout(); - throw new Error('다시 로그인해주세요.'); - } - - const data: SpotifyAuthResponse = await response.json(); - this.setToken(data.access_token); - this.setExpiresAt(Date.now() + data.expires_in * 1000); + return response; } /** * 로그아웃 */ logout(): void { - this.removeToken(); - this.removeRefreshToken(); - this.removeExpiresAt(); + this.removeSessionId(); + this.removeSessionExpiresAt(); } /** * 백엔드 API를 통해 플레이리스트 목록 가져오기 */ async getPlaylistsFromBackend(): Promise> { - // 토큰 유효성 확인 및 자동 갱신 - const token = await this.getValidToken(); + const sessionId = this.getSessionId(); - if (!token) { + if (!sessionId) { return { success: false, error: '로그인이 필요합니다.', }; } - return await apiClient.post('/v2/spotify/playlists', { - access_token: token, + return await apiClient.get('/v2/spotify/playlists', { + headers: { + 'X-Session-Id': sessionId, + }, }); } @@ -277,101 +230,58 @@ class SpotifyService { * 백엔드 API를 통해 트랙 추가 */ async addTracksToPlaylist(request: SpotifyAddTracksRequest): Promise> { - // 토큰 유효성 확인 및 자동 갱신 - const validToken = await this.getValidToken(); + const sessionId = this.getSessionId(); - if (!validToken) { + if (!sessionId) { return { success: false, error: '로그인이 필요합니다.', }; } - // 유효한 토큰으로 업데이트 - const updatedRequest = { - ...request, - access_token: validToken, - }; - - return await apiClient.post('/v2/spotify/playlists/tracks', updatedRequest); + return await apiClient.post('/v2/spotify/playlists/tracks', request, { + headers: { + 'X-Session-Id': sessionId, + }, + }); } /** * 로그인 상태 확인 */ isLoggedIn(): boolean { - const token = this.getToken(); - const expiresAt = this.getExpiresAt(); + const sessionId = this.getSessionId(); + const expiresAt = this.getSessionExpiresAt(); - if (!token || !expiresAt) return false; + if (!sessionId || !expiresAt) return false; return Date.now() < expiresAt; } - /** - * 유효한 토큰 가져오기 (만료 시 자동 갱신) - */ - private async getValidToken(): Promise { - const token = this.getToken(); - const expiresAt = this.getExpiresAt(); - - if (!token) { - return null; - } - - // 토큰이 만료되었거나 5분 이내에 만료될 예정이면 갱신 - const fiveMinutes = 5 * 60 * 1000; - if (!expiresAt || Date.now() + fiveMinutes >= expiresAt) { - console.log('Spotify 토큰이 만료되었거나 곧 만료됩니다. 토큰을 갱신합니다.'); - - try { - await this.refreshAccessToken(); - return this.getToken(); - } catch (error) { - console.error('토큰 갱신 실패:', error); - return null; - } - } - - return token; + // Session 관리 메서드 + private setSessionId(sessionId: string): void { + localStorage.setItem(this.sessionIdKey, sessionId); } - // Token 관리 메서드 - private setToken(token: string): void { - localStorage.setItem(this.tokenKey, token); + getSessionId(): string | null { + return localStorage.getItem(this.sessionIdKey); } - getToken(): string | null { - return localStorage.getItem(this.tokenKey); + private removeSessionId(): void { + localStorage.removeItem(this.sessionIdKey); } - private removeToken(): void { - localStorage.removeItem(this.tokenKey); + private setSessionExpiresAt(timestamp: number): void { + localStorage.setItem(this.sessionExpiresAtKey, timestamp.toString()); } - private setRefreshToken(token: string): void { - localStorage.setItem(this.refreshTokenKey, token); - } - - private getRefreshToken(): string | null { - return localStorage.getItem(this.refreshTokenKey); - } - - private removeRefreshToken(): void { - localStorage.removeItem(this.refreshTokenKey); - } - - private setExpiresAt(timestamp: number): void { - localStorage.setItem(this.expiresAtKey, timestamp.toString()); - } - - private getExpiresAt(): number | null { - const value = localStorage.getItem(this.expiresAtKey); + private getSessionExpiresAt(): number | null { + const value = localStorage.getItem(this.sessionExpiresAtKey); return value ? parseInt(value, 10) : null; } - private removeExpiresAt(): void { - localStorage.removeItem(this.expiresAtKey); + private removeSessionExpiresAt(): void { + localStorage.removeItem(this.sessionExpiresAtKey); } // PKCE 유틸리티 메서드 diff --git a/src/types/api.ts b/src/types/api.ts index ff184a3..18098e1 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -161,6 +161,23 @@ export interface SpotifyTrack { uri: string; } +// Spotify OAuth 콜백 요청 타입 +export interface SpotifyAuthCallbackRequest { + access_token: string; + token_type: string; + expires_in: number; + refresh_token?: string; + scope: string; +} + +// Spotify OAuth 콜백 응답 타입 +export interface SpotifyAuthCallbackResponse { + success: boolean; + message: string; + session_id: string; + expires_in: number; +} + // Spotify 플레이리스트 목록 조회 응답 (백엔드 API) export interface SpotifyBackendPlaylistsResponse { success: boolean; @@ -170,7 +187,6 @@ export interface SpotifyBackendPlaylistsResponse { // Spotify 트랙 추가 요청 타입 export interface SpotifyAddTracksRequest { - access_token: string; playlist_id?: string | null; playlist_name?: string; playlist_description?: string;