일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 2023 KAKAO BLIND RECRUITMENT
- 내성적인 건물주
- 부자의 그릇
- 리코쳇 로봇
- for ... in
- 1권 1진리
- 요격 시스템
- css
- fontweight
- react native
- decodeURIComponent
- 연속된 부분 수열의 합
- 투포인터
- js
- woff2
- for ... of
- 프로그래머스
- keypress
- 2023 카카오 블라인드 채용
- 알고리즘
- 택배 배달과 수거하기
- level2
- window.onload
- DOMContentLoaded
- keyup
- 저는 이 독서법으로 연봉 3억이 되었습니다.
- 이즈미 마사토
- 코딩테스트
- custom font
- TypeScript
- Today
- Total
스카이코의 세상
Access Token 다중 요청 문제 해결 본문
JWT(Json Web Token)
Access Token은 JWT 인증 방식에서 사용되는 토큰 중 Client가 가지고 있는 토큰입니다.
JWT란 이름 그대로 Json 형태의 토큰으로 인증에 필요한 정보가 암호화되어 저장되어 있습니다.
JSON 데이터를 Base64 URL-safe Encode를 통해 인코딩하여 직렬화 한것으로 위변조 방지를 위하여 개인키를 사용한 전자서명도 들어있어 서버에서 검증할 수 있습니다.
검증 후 이상이 없다고 판단되면 요청한 응답을 돌려줄 수 있는 것이지요.
만약 Access Token이 탈취된다면 어떻게 될까요?
Server 입장에서는 탈취된 Access Token을 받고 정상적인 요청으로 판단하여 응답을 돌려주는 문제가 발생합니다.
Access Token은 Client에서 관리되므로 발급 후 Server에서 대응할 수 없기 때문에 유효 시간을 짧게 설정하여 탈취 문제에 대응합니다.
하지만 유효 시간이 짧으면 Access Token을 재발급 받는 과정, 즉 로그인을 자주 해야 하는 문제가 발생합니다.
유효 시간이 30분이면 30분마다 로그인을 새로 해야 하는 문제가 발생하는 것이지요.
이 문제를 해결하기 위하여 Access Token의 재발급에 관여하는 또 다른 JWT 토큰인 유효 시간이 긴 Refresh Token을 추가로 사용합니다.
사용자가 유효 시간이 만료될 때마다 로그인을 다시 하는 것 대신에 Access Token을 재발급해주는 역할을 하는 Refresh Token을 Server에 두어 Access Token을 재발급 받는 것이지요.
어떤 상황에서 문제가 발생할까?
한 화면에서 여러 API 요청을 보내는 경우가 많습니다. 사용자 정보도 불러와야하고 필요한 데이터 목록도 같이 불러오고 필터 정보도 불러오고 합니다.
하지만 Access Token이 만료된 상태에서 이러한 요청들을 보내면 어떻게 될까요?
여러 개의 API 요청이 모두 Access Token이 만료됨을 확인하고 발급 요청을 여러 번 보내게 될 것입니다.
위 그림을 통하여 문제 상황을 예로 들어보겠습니다.
첫 번째 API Call에서 Access Token이 만료되어 토큰을 재 요청 하였습니다.
하지만 그 사이 두 번째 API Call이 일어났지만 여전히 Access Token이 만료된 상태이므로 토큰 재 요청을 하지만 이번에는 실패합니다.
그 이유는 Access Token API를 통하여 Server의 Refresh Token은 갱신하였지만, 두 번째 API Call의 토큰 재 요청에서 보낸 Refresh Token 값은 갱신 되기 전 Client의 Refresh Token 값이기 때문입니다.
그리고 Access Token과 Refresh Token을 제대로 세팅한 이후의 요청(3, 4)은 다시 성공하고 있는 모습을 볼 수 있습니다.
발급 요청을 여러 번 보내면서 API Call이 늘어나는 것도 문제이지만 API 호출 순서가 응답 순서를 보장하지 않으므로 요청 당시에 Server와 Client 사이에 서로 다른 토큰 값을 저장하는 문제도 발생합니다. 이는 곧 다시 API Call이 늘어나는 문제를 야기할 수 있습니다.
어떻게 해결할 수 있을까?
이 문제를 어떻게 해결할 수 있을까요?
가장 먼저 떠오르는 방식은 Access Token을 요청하는 동안은 다른 요청을 보내지 않는 것입니다.
Client에서는 axios를 사용할 때 interceptor를 통하여 응답을 보내기 전, 받은 후에 대한 처리를 진행할 수 있습니다.
사실 기존의 코드에서 Access Token을 요청할 때도 interceptor.response, 응답을 받은 후에 대한 처리를 추가하여 토큰을 재 요청 하였습니다.
axiosInstance.interceptors.response.use(
(value) => value,
async (error) => {
// AT가 없을 때
if (!isAccessToken) {
// RT도 없는 경우
if (!isRefreshToken) {
// 로그인 페이지로 redirect
await redirectLogin();
return Promise.reject(error);
}
// accessToken 재요청 API
return requestAccessToken(config);
}
...
);
이제 이 부분을 개선하여 Access Token을 요청하는 동안에는 다른 요청을 보내지 않도록 처리해야 합니다. 어떻게 처리할 수 있을까요?
뮤텍스(Mutex)의 개념을 적용하면 쉽게 해결할 수 있습니다. 뮤텍스란 상호 배제(mutual exclusion)의 약자로 자원에 대한 접근 제한을 위한 동기화 메커니즘입니다.
쉽게 말해 동시에 하나의 자원에 동시에 접근하지 못하게 하는 것입니다. 위 상황에서는 Access Token을 요청하는 API에 대한 접근을 제한하는 것이지요. 즉 Access Token을 요청하는 동안에는 또 다른 Access Token 요청 API를 보내지 않는 것입니다.
뮤텍스를 적용하기 위해 JS의 Promise와 변수를 사용하여 API Call의 동작을 조절할 수 있습니다.
// accessToken Api Promise 저장하는 변수
let accessTokenPromise = null;
const requestAccessToken = async (config) => {
// 액세스 토큰 요청 중이 아닐 경우
if (!accessTokenPromise) {
// 액세스 토큰 요청 API의 promise 저장
accessTokenPromise = accessTokenApiCall();
}
try {
// 액세스 토큰 요청 중일 경우 promise 끝날 때 까지 대기
await accessTokenPromise;
accessTokenPromise = null;
return axiosInstance(config);
} catch (e) {
accessTokenPromise = null;
return Promise.reject(e);
}
};
위 코드에서 accessTokenPromise이라는 변수를 통하여 현재 API Call이 진행 중인지 판단하고 Promise를 사용하여 이후 요청들을 제어할 수 있습니다.
최초 Access Token 요청이라면 accessTokenPromise 값에 Promise를 저장하여 요청 중 상태로 변경하고 다른 Access Token이 필요하다는 요청이 들어올 경우 최초 Access Token 요청이 끝날 때 까지 기다립니다.
이후에 최초로 보냈던 Access Token 요청에 성공적으로 응답이 온다면 accessTokenPromise의 Promise가 진행되며 기다렸던 요청들을 모두 보낼 수 있고 null로 변경함으로써 진행 중 상태를 종료시킵니다.
그리고 한 가지 주의할 부분이 있는데 Access Token 요청을 보내는 중에는 다른 Access Token 요청 뿐만 아니라 모든 API 요청을 대기하여야 합니다.
그 이유는 위 그림과 같이 Access Token 요청 중 다른 API 요청이 들어왔는데
2번 케이스처럼 Access Token 요청이 끝나기 전에 실패 응답을 받는다면 Access Token 요청이 진행 중이므로 요청이 끝난 후 정상적으로 API Call을 진행하므로 불필요한 API 요청이 발생합니다.
그리고 더욱 문제가 되는 부분은 3번 케이스인데 API Call이 Access Token API 응답 이후에 완료된다면 Access Token API를 다시 호출하여 불필요한 API 요청 뿐만 아니라 토큰도 다시 세팅하게 되는 문제가 발생합니다.
axiosInstance.interceptors.request.use(async (config) => {
// AccessToken 요청 중일 경우 그 외의 요청은 promise를 대기한다.
if (accessTokenPromise && !config.url?.includes('/access-token-api')) {
await accessTokenPromise;
}
...
});
그러므로 interceptor.request에서 accessTokenPromise값을 통하여 Access Token API가 요청 중인 경우에는 끝날 때 까지 대기하는 로직을 추가하여 문제를 해결하여 줍니다.
또한 하나 더 주의할 점은 Access Token API 요청 자체도 axios intercaptor를 통하므로 해당 요청은 로직에서 제외해야 합니다.
이제 위의 로직을 총 정리하여 반영하였을 경우 다음과 같이 Access Token 만료가 발생하더라도 Access Token API 요청을 한번만 수행하는 결과를 얻을 수 있습니다.
'IT > 개발' 카테고리의 다른 글
CRLF vs LF (0) | 2023.08.13 |
---|---|
HTML에 SVG를 추가하는 방법 (2) | 2023.01.07 |
시맨틱 버저닝(Semantic Versioning) (2) | 2023.01.05 |