Skip to content

Commit a3e2ab3

Browse files
authored
Merge pull request #20 from DguFarmSystem/feat/#4
feat: apiConfig.ts에 인터셉터 추가하여 엑세스 토큰이 만료되었을 때(401) 토큰 재발급 기능 추가
2 parents d075140 + ecd38ab commit a3e2ab3

File tree

2 files changed

+85
-8
lines changed

2 files changed

+85
-8
lines changed

src/config/apiConfig.ts

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,74 @@
1-
import axios from 'axios';
1+
import axios from "axios";
2+
import Cookies from "js-cookie";
3+
import { refreshAccessToken } from "../services/authService";
24

35
const getApiBaseUrl = () => {
4-
const baseUrl = import.meta.env.VITE_BASE_DEV_URL; // 개발 서버 URL
5-
6-
// URL이 '/'로 끝나면 'api/', 아니면 '/api/'
7-
return baseUrl.endsWith('/') ? `${baseUrl}api/` : `${baseUrl}/api/`;
6+
const baseUrl = import.meta.env.VITE_BASE_URL;
7+
return baseUrl.endsWith("/") ? `${baseUrl}api/` : `${baseUrl}/api/`;
88
};
99

1010
const API_BASE_URL = getApiBaseUrl();
1111

1212
const apiConfig = axios.create({
1313
baseURL: API_BASE_URL,
14-
timeout: 5000, // 요청 타임아웃 5초
14+
timeout: 5000,
1515
headers: {
16-
'Content-Type': 'application/json',
16+
"Content-Type": "application/json",
1717
},
18+
withCredentials: true,
1819
});
1920

20-
export default apiConfig;
21+
// 요청 인터셉터: 모든 요청에 자동으로 엑세스 토큰 추가
22+
apiConfig.interceptors.request.use(
23+
(config) => {
24+
const accessToken = localStorage.getItem("accessToken");
25+
if (accessToken) {
26+
config.headers.Authorization = `Bearer ${accessToken}`;
27+
}
28+
return config;
29+
},
30+
(error) => Promise.reject(error)
31+
);
32+
33+
// 401 발생 시 자동으로 토큰 재발급 후 요청 재시도
34+
apiConfig.interceptors.response.use(
35+
(response) => response,
36+
async (error) => {
37+
const originalRequest = error.config;
38+
39+
// 401 Unauthorized 발생 시, 자동으로 토큰 재발급
40+
if (error.response?.status === 401 && !originalRequest._retry) {
41+
originalRequest._retry = true;
42+
43+
try {
44+
const refreshToken = Cookies.get("refreshToken"); // 쿠키에서 리프레시 토큰 가져오기
45+
if (!refreshToken) {
46+
console.error("리프레시 토큰 없음 → 재로그인 필요");
47+
localStorage.removeItem("accessToken");
48+
Cookies.remove("refreshToken");
49+
window.location.href = "/login"; // 로그인 페이지로 이동
50+
return Promise.reject(error);
51+
}
52+
53+
// ✅ 엑세스 토큰 재발급 요청
54+
const newTokens = await refreshAccessToken(localStorage.getItem("accessToken")!);
55+
localStorage.setItem("accessToken", newTokens.accessToken);
56+
Cookies.set("refreshToken", newTokens.refreshToken, { secure: true, sameSite: "Strict" });
57+
58+
// ✅ 새로운 토큰으로 원래 요청 다시 실행
59+
originalRequest.headers.Authorization = `Bearer ${newTokens.accessToken}`;
60+
return apiConfig(originalRequest);
61+
} catch (refreshError) {
62+
console.error("토큰 갱신 실패 → 재로그인 필요");
63+
localStorage.removeItem("accessToken");
64+
Cookies.remove("refreshToken");
65+
window.location.href = "/login"; // 로그인 페이지로 이동
66+
return Promise.reject(refreshError);
67+
}
68+
}
69+
70+
return Promise.reject(error);
71+
}
72+
);
73+
74+
export default apiConfig; // ✅ 모든 API 요청에서 사용 가능!

src/services/authService.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import apiConfig from "../config/apiConfig";
2+
import Cookies from "js-cookie";
3+
4+
5+
export const refreshAccessToken = async (accessToken: string) => {
6+
try {
7+
const refreshToken = Cookies.get("refreshToken");
8+
if (!refreshToken) {
9+
console.error("리프레시 토큰이 없습니다. 재로그인 필요");
10+
throw new Error("리프레시 토큰 없음");
11+
}
12+
13+
const response = await apiConfig.post(`/api/auth/reissue`, {
14+
accessToken,
15+
refreshToken,
16+
});
17+
18+
return response.data;
19+
} catch (error: any) {
20+
console.error("토큰 재발급 실패:", error.response?.data?.message || error.message);
21+
throw error;
22+
}
23+
};

0 commit comments

Comments
 (0)