너는 JWT를 모른다

iubns·2025년 12월 6일
post-thumbnail

이 글은 기본적인 JWT와 세션 등의 인증 개념을 알고 있다는 전제를 바탕으로 작성되었습니다. 만약 잘 모르신다면, 먼저 관련 글을 읽고 오시길 권장드립니다.

로그인과 데이터 접근 제어는 현대의 서비스에서 빠질 수 없는 기능입니다.

하지만, 이렇게 중요한 접근 제어 당신은 잘 하고 있나요?

[최근 접근제어 잘못 해서 크게 혼나고 있는 모 회사]

뭐?.. refresh token을 localStorage에 넣는다고?

블로그나 인터넷에 보면 정말 수 많은 개발자들이 실수 하는 부분중에 하나이다.
access token과 refresh token을 로그인시 모두 받은 뒤에 둘다 localStorage에 넣는 경우이다.

이것이 왜 문제일까?
로컬 스토리지에 있는 토큰은 xss 공격에 취약하기 때문이다.

xss 공격은 여러 경로를 통하여 당신의 사이트에 js 코드를 심는 공격이며, 만약 특정 게시물에 xss 공격을 하였다면 해당 게시물 방문자 모두는 localstorage.getitem()가 실행되며 토큰을 탈취당하게 된다.

따라서, 사실 refresh token은 프론트에서 접근이 가능하면 안된다.
한번 탈취 당하면 갱신을 통하여 계속적으로 만료 시간을 연장 할 수 있기 때문

[잘못된 방법을 알려주고 있는 다른 블로그 내용, 왜 거꾸로?...]

그러면 access token은 탈취 당해도 되는가?

물론 아니다, 하지만 access token 마저 httpOnly와 같이 프론트에서 접근할 수 없는 곳에 토큰 정보를 넣는다면 왜 jwt를 사용하는가?
(서버에서의 db 조회 안해도 되는 이점이 남지만, 클라이언트에서의 접근 권한 제어라는 큰 장점 중 하나를 잃어버리게 된다.)

그렇다면 어떻게 해야 하는가?

access token이 탈취될 경우를 생각하여 만기를 매우 짧게 (5~30분)하고 refresh token을 사용하여 지속적으로 갱신해야 한다.

좋다, 이제 refresh token을 이용하여 access token을 갱신해보자.

잠깐.. refresh token이 변하지 않는다고?

refresh token은 모든 정보에 접근을 할 수 있는 access token을 발급 할 수 있게 해주는 토큰으로 매우 중요하다.

refresh token이 탈취 당하지 않도록 많은 노력을 하지만 이 또한 수 많은 방법으로 노출이 될 수 있다.
만약, refresh token이 access token을 갱신할 때 새로 발급이 되지 않는다면 어떻게 될까?

[무한으로 즐겨요~ access token]

따라서, access token이 갱신될때 refresh token도 같이 갱신이 되어야 하며 유효한 refresh token인지는 db에 기록하여 세션처럼 검증해야 한다.
(만약, db에서 검증하지 않는다면 무제한 증식 refresh token을 볼 수 있게 된다. 무한이 두배?!?!)

하지만.. 이미 해커가 가져가서 갱신을 해버렸으면?..

사용자의 refresh token은 무효가 되게 된다.
따라서 이후 access token이 만료 되었을때, refresh token을 제출하게 되고 이때 연장이 불가해지기에 로그아웃이 되게 된다.
이에 불편함을 느끼고 사용자가 다시 로그인을 하게 되면 해커의 토큰이 만료 되게 된다.

뭔가 이상하다. 만약 로그인을 사용자가 다시 하지 않는다면?...

그래서, 유효하지 않은 refresh token이 들어왔을 경우 해당 계정에 대한 해킹 시도로 보고 서버단에서 access token을 재발급 해주지 않는 것이 바람직하다.

뭐?... token만 지우고 로그아웃 완료?!

위에서 말 하였듯이 해커가 token을 가져간 뒤에 로그인을 하지 않으면 해커가 가져간 토큰은 계속 유효해진다. 하지만 비슷한 사례가 하나 더 있다.

바로, token만 지우고 로그아웃 처리를 하는 것이다.

[잘못된 설명을 하는 또 다른 블로그, 아!..안돼!!!]

클라이언트에서 refresh token을 임의로 지워버리면 서버쪽에선 아직 유효한 토큰이 되게 된다. 만약 이미 해커에게 해당 토큰이 넘어간 상황이라면 해커는 토큰을 씹고 뜯고 맛보고 즐기게 될 것이다.

사용자가 로그아웃을 요청 할 경우 서버로 로그아웃 요청을 보내 refresh token의 db 정보를 지워야 한다.

사용자가 그냥 닫기버튼으로 브라우저를 끄면요?..

sendBeacon 를 사용하여 로그아웃 api를 호출해야 한다.

access token의 만료 시간이 정말 짧아도 될까?

지금까지의 이야기를 들어보면 access token의 시간은 짧게 하고 refresh token으로 노출의 위험을 방지한다는 것을 알게 되었을 것이다.

그렇다면 이러한 의문이 들 수 있다.

refresh token은 자주 사용하지 않기에 노출 위험이 적은거 아닌가?
자주 갱신하면 자주 노출되는거 아닌가?

정답은 "아니다" 이다.
요즘 대부분의 사이트들은 https를 사용하며 수 많은 브라우저들은 https를 강제하고 있다.
(당장 크롬만 봐도 자물쇠 모양을 보여주며 여기 위험해!라고 알려주며 ios는 아에 api 호출이 안된다)

https의 경우 서버와 브라우저의 e2e 암호화가 되기에 중간에 데이터를 보기 힘들며
설령 "중간자 공격으로 해킹을 하여 refresh token을 가로챌 수 있지 않느냐?" 라고 한다면
이미 중간자 공격에 성공한 순간 모든걸 할 수 있는 상태이기에 refresh token 노출은 사소해진다.
(무엇보다 시간을 늘린다고 해서 막아지지 않는다.)

따라서 access token의 시간이 적을 수록 보안이 좋아진다.
(refresh token의 갱신을 위한 db io 성능이 올라가겠지만...)

[아.. 그..그거 아닌데]

refresh token은 어떻게 사용해야 할까?

좋다. 이제 좀 알겠다.
localStorage에 넣으면 안되는 것도 알겠고, 사용할때 마다 새로 갱신 되어야 하는것도 알겠다. 근데 그래서 어떻게 써야 하는데?

쿠키는 https 요청시마다 자동으로 날라가게 된다.

만약, refresh token을 그냥 일반 도메인에 httpOnly로 넣게 된다면 모든 요청마다 헤더에 붙어서 날라가게 될 것이다. 물론 위에서 말 하였듯이 https의 경우 종단 암호화가 되기에 노출을 크게 신경쓰진 않아도 되지만 불필요한 헤더가 네트워크 비용을 잡아 먹게 된다.

필요할때만 refresh token을 보내는 방법은 없을까?

해결은 도메인 분리!

auth.domain.any로 도메인을 분리하여 access token이 만료되었을 경우 해당 도메인으로 재발급 요청을 하면 된다. 이후 받은 access token을 localStorage나 세션등 js가 접근 가능한 공간에 넣고 원래 도메인으로 요청시 header에 access token을 넣는 방법을 사용하자.

아! refresh token도 새로 바꿔야 하지 않냐고?
걱정마라 백엔드 개발자가 해줄것이다

해주지 않는다면 이 글을 공유해주도록 하자

(좋은말 할때 쿠키로 해주시죠?^^)

profile
구직중인 26년 2월 졸업, 중고신입

0개의 댓글