
--- client/resources/api/index.js
+++ client/resources/api/index.js
... | ... | @@ -1,7 +1,22 @@ |
1 | 1 |
import axios from 'axios'; |
2 | 2 |
import store from "../../views/pages/AppStore"; |
3 | 3 |
|
4 |
-// 공통 API 요청 인터셉터 설정 |
|
4 |
+// JWT 토큰 디코더 |
|
5 |
+function decodeToken(token) { |
|
6 |
+ try { |
|
7 |
+ const base64String = token.split('.')[1]; |
|
8 |
+ const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/'); |
|
9 |
+ const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => |
|
10 |
+ '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) |
|
11 |
+ ).join('')); |
|
12 |
+ return JSON.parse(jsonPayload); |
|
13 |
+ } catch (e) { |
|
14 |
+ console.error("Invalid token", e); |
|
15 |
+ return null; |
|
16 |
+ } |
|
17 |
+} |
|
18 |
+ |
|
19 |
+// 공통 API 클라이언트 |
|
5 | 20 |
const apiClient = axios.create({ |
6 | 21 |
baseURL: '/', |
7 | 22 |
headers: { |
... | ... | @@ -9,83 +24,65 @@ |
9 | 24 |
} |
10 | 25 |
}); |
11 | 26 |
|
12 |
-// 요청 인터셉터 |
|
27 |
+// 요청 인터셉터 - 토큰이 있으면 Authorization 헤더 추가 |
|
13 | 28 |
apiClient.interceptors.request.use( |
14 | 29 |
config => { |
15 |
- // 요청 전에 토큰을 헤더에 추가 |
|
16 |
- const token = store.state.authorization; |
|
17 |
- if (token) { |
|
30 |
+ if (store.state.authorization) { |
|
18 | 31 |
config.headers.Authorization = store.state.authorization; |
19 | 32 |
} |
20 | 33 |
return config; |
21 | 34 |
}, |
22 |
- error => { |
|
23 |
- return Promise.reject(error); |
|
24 |
- } |
|
35 |
+ error => Promise.reject(error) |
|
25 | 36 |
); |
26 | 37 |
|
27 | 38 |
// 응답 인터셉터 |
28 | 39 |
apiClient.interceptors.response.use( |
29 |
- response => response, |
|
40 |
+ response => { |
|
41 |
+ return response |
|
42 |
+ }, |
|
30 | 43 |
async error => { |
44 |
+ const originalReq = error.config; |
|
31 | 45 |
if (!error.response) return Promise.reject(error); |
32 | 46 |
|
33 |
- const { status, data } = error.response; |
|
34 |
- const originalReq = error.config; |
|
35 |
- |
|
36 |
- // 로그인 요청은 토큰 리프레시 대상이 아님 |
|
47 |
+ // 로그인 요청 |
|
37 | 48 |
if (originalReq?.skipAuthRefresh) { |
38 | 49 |
return Promise.reject(error); |
39 | 50 |
} |
40 | 51 |
|
41 | 52 |
// 권한 없음 |
42 |
- if (status === 403 && data.message === '접근 권한이 없습니다.') { |
|
53 |
+ if (error.response.status === 403 && error.response.data.message === '접근 권한이 없습니다.') { |
|
43 | 54 |
window.history.back(); |
44 | 55 |
return Promise.reject(error); |
45 | 56 |
} |
46 |
- |
|
47 |
- // 리프레시 토큰 요청은 재시도하지 않음 |
|
57 |
+ |
|
58 |
+ // 리프레시 토큰 요청 |
|
48 | 59 |
if (originalReq.url.includes('/refresh/tknReissue.json')) { |
49 | 60 |
return Promise.reject(error); |
50 | 61 |
} |
51 | 62 |
|
52 | 63 |
// 토큰 만료 시 한 번만 재시도 |
53 |
- if (status === 401 && !originalReq._retry) { |
|
64 |
+ if (error.response.status === 401 && !originalReq._retry) { |
|
54 | 65 |
originalReq._retry = true; |
55 |
- |
|
56 | 66 |
try { |
57 |
- // 리프레시 요청은 별도 인스턴스로 처리 (apiClient로 하면 무한 루프 위험) |
|
58 |
- const refreshClient = axios.create(); |
|
59 |
- const res = await refreshClient.post('/refresh/tknReissue.json'); |
|
67 |
+ const res = await axios.post('/refresh/tknReissue.json',{}); |
|
60 | 68 |
const newToken = res.headers.authorization; |
61 | 69 |
|
62 | 70 |
// 토큰 저장 |
63 | 71 |
store.commit('setAuthorization', newToken); |
64 |
- originalReq.headers.Authorization = newToken; |
|
72 |
+ originalReq.headers.Authorization = store.state.authorization; |
|
65 | 73 |
|
66 | 74 |
// 유저 정보 다시 저장 |
67 |
- /** jwt토큰 디코딩 **/ |
|
68 |
- const base64String = store.state.authorization.split('.')[1]; |
|
69 |
- const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/'); |
|
70 |
- const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => { |
|
71 |
- return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); |
|
72 |
- }).join('')); |
|
73 |
- const user = JSON.parse(jsonPayload); |
|
75 |
+ const user = decodeToken(newToken); |
|
74 | 76 |
store.commit("setUserInfo", { |
75 |
- userId: user.userId, |
|
76 |
- loginId: user.loginId, |
|
77 | 77 |
userNm: user.userNm, |
78 |
+ loginId: user.loginId, |
|
79 |
+ userId: user.userId, |
|
78 | 80 |
roles: Array.isArray(user.roles) ? user.roles.map(r => r.authority) : [], |
79 | 81 |
}); |
80 | 82 |
|
81 | 83 |
// 실패했던 요청 재시도 |
82 | 84 |
return apiClient(originalReq); |
83 | 85 |
} catch (refreshError) { |
84 |
- // 로그인 요청은 리프레시 실패 시에도 처리 |
|
85 |
- if (originalReq?.url?.includes('/login.json')) { |
|
86 |
- return Promise.reject(refreshError); |
|
87 |
- } |
|
88 |
- |
|
89 | 86 |
// 리프레시 실패 - 세션 만료 처리 |
90 | 87 |
sessionStorage.setItem("redirect", window.location.pathname + window.location.search); |
91 | 88 |
alert('세션이 종료 되었습니다.\n로그인을 새로 해주세요.'); |
... | ... | @@ -101,4 +98,4 @@ |
101 | 98 |
} |
102 | 99 |
); |
103 | 100 |
|
104 |
-export default apiClient;(파일 끝에 줄바꿈 문자 없음) |
|
101 |
+export default apiClient; |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?