오늘의 개발

JWT 액세스 토큰을 언제 재발급 받아야하지? 리프레시 토큰 첫 사용 고민기

유토냥이 2023. 4. 16. 13:52

💙 JWT 리프레시 토큰 도대체 어떻게 쓰는거지?

헤더에 JWT 토큰을 실어보낸 적은 이전에 한 차례 있었지만, 리프레시 토큰을 사용하는 것은 이번이 처음이다.

헙, 그런데 액세스 토큰이 만료되기 전에 리프레시 토큰을 서버로 보낸 후 새 액세스 토큰을 발급 받아야 한다는 것은 알고 있는데

도대체 언제 이 만료된 토큰을 전달해야하는거지..?  🤔

 

어떤 방법이 맞는지 아직 모르므로 일단 생각해본 내용을 적어보고

구글링해서 다른 사람들은 어떻게 사용하는지 찾아볼 예정이다.

고민해본 내용은 다음과 같다.

 

1. 액세스 토큰 만료 기간을 추적하여 n분 전에 서버와 통신하여 새 액세스 토큰으로 발급 받는다.

- 그러면 액세스 토큰의 exp를 주기적으로 계산할 필요가 있을 것 같다.

현재 액세스 토큰의 만료 시간은 1시간으로 설정되어있는데 세션 스토리지에 토큰을 올린 이후부터 일정 시간을 두어 주기적으로 체크한다..?

 

2. 매 서버 통신(request) 요청 시 액세스 토큰이 유효한지 확인하고,

서버로부터 401 상태 코드를 반환받으면 그때 액세스 토큰을 새로 발급받는다. (401로 해주신 줄 알았는데 400이었다.)

- 그렇다면 서버 통신을 할 때마다 토큰 유효를 확인해야 하는걸까?

현재 axios를 사용하고 있으므로 인터셉터를 통해 액세스 토큰 유효에 대해 먼저 확인하는 방법이 있을 것 같다.

 

음, 그런데 생각해보니 한가지 더 궁금한 사항이 생겼다.

어쨌든 1번이나 2번을 수행하더라도 서버에게 리프레시 토큰을 전달해야 새 액세스 토큰을 발급 받을 수 있을 텐데

리프레시 토큰도 세션 스토리지에 저장해야하는건가? 🤔🤔🤔

액세스 토큰이 탈취 위험이 있어서 리프레시 토큰을 사용하는 거 아니였나?

그러면 리프레시 토큰도 스토리지에 저장하면 똑같이 탈취 위험이 있는 거 아닌가?

그렇다면 리프레시 토큰을 어디에 저장해야하는거지..?ㅋㅋㅋㅋㅋ (무한의 인피니트)

 

이미지 출처: 몽글이

누가 내 옆에 있었으면 물음표로 살인했을 것 같다.

 

리프레시 토큰을 어디에 보관할까?

1. 스토리지에 저장하기 전 클라이언트에서 암호화해서 스토리지에 저장한다. (암호화하면 복호화도 해야하네... 깔깔)

2. 쿠키에 저장한다는 얘기도 예전에 얼핏 본 것 같은데, 쿠키에 저장하면 뭐가 다른가..? (잘 모르므로 서치 예정)

 

쿠키에 리프레시 토큰을 담아도 되는지 서치해보고 왔다.

잘 정리된 블로그 글이 있어서 읽어봤는데 secure에 httpOnly 쿠키로 리프레시 토큰을 저장한다는 것 같다.

 

하지만 현재 본인은 서버로부터 로그인 후에 액세스 토큰과 리프레시 토큰을 함께 전달받고 있는데

당장은 서버 응답 헤더에 Set-Cookie 헤더가 없으므로 httpOnly 쿠키를 사용할 수 없다.

따라서 다음과 같은 보안 이슈가 있다.

 

 

현재 로그인에 성공하면 서버로부터 액세스 토큰과 리프레시 토큰이 응답으로 body에 담겨져 온다.

body로 넘겨주고 있으므로 탈취 당하면 보안상 이슈가 있지 않을까?

리프레시 토큰은 HttpOnly, Secure 속성이 설정된 쿠키를 통해 사용하는 것이 안전할 것으로 본다.

 

이건 사실상 아무리 클라이언트에서 보안을 챙긴다고 해도 응답 본문을 탈취당하면 무용지물이 아닐까 싶기는한데,

이 부분은 클라이언트에서 해결할 수 있는 방법은 아니므로.....ㅠ

나중에 가능하다면 해당 이슈에 대해 백엔드 팀원분과 논의해봐야겠다.

따라서 현재 주어진 조건에서 가능한 클라이언트에서 할 수 있는 방법 중 최선을 선택하고자 한다.


💙 차선책, 리프레시 토큰 암호화

서버에서 Set-cookie 설정이 되어있지 않으므로, 클라이언트의 최선이자 차선의 방법인

응답으로 받은 리프레시 토큰을 암호화하여 세션 스토리지에 저장하는 방식으로 선택하기로 했다.

 

암호화를 위해 crypto-js 라이브러리를 설치했다.

이제 AES 알고리즘을 사용하여 리프레시 토큰을 암호화할 것이다.

암호화 코드

리프레시 토큰을 암호화 한 후 세션 스토리지에 저장시켰다.

액세스 토큰을 새로 발급 받을 때는 리프레시 토큰을 다시 복호화 해야 한다.

 

암호화된 리프레시 토큰

 

현재 액세스 토큰을 가지고 디코드 후에 해당 유저의 닉네임 정보를 사용하고 있는데 닉네임을 변경했을 경우 디코드 토큰에는 반영되지 않는다는 문제점이 있었다. 재로그인 전까지 변경 전 닉네임으로 사용하게 된다.

이를 해소하고자 강제 로그아웃시키면 UX가 매우 부정적일 것으로 예상하여 닉네임 변경 시 액세스 토큰을 재발급시키기로 했다.

그런데 액세스 토큰을 계속 재발급 요청한다면 서버 부하가 증가할 것 같아서 그다지 좋은 방법은 아닌 것 같다.

닉변 한번에 액세스 토큰 한번 교체..? 강제로 프로필 변경에 시간 제한을 두지 않는 이상은 n초당 액세스 토큰 재발급이 가능한 시나리오가 된다.

 

사실 닉네임에 대해 중요한 로직이 없으면 다음 로그인에 닉네임 변경을 반영시켜도 큰 문제는 없을지도 모르겠지만,

팀원 분이 작업중인 페이지에서 약간의 버그가 있으므로 (닉네임을 가지고 하는 로직이 있어서) 리프레시 토큰 복호화 테스트를 위해 이 부분을 먼저 진행해보기로 했다.

 

그런데 아무리 생각해도 닉네임 변경에 토큰 재발급은 호미로 막을 것을 가래로 막는 느낌이다.

제일 베스트는 로직을 변경될 여지가 있는 닉네임이 아니라 고정된 pk값으로 했어야 사이드 이펙트가 생기지 않았을 것이다.

 

구글에서 주워옴

 

아무래도 닉네임 로직을 처음부터 잘못 쓴 것 같다.......흐우우ㅜㅠ

마치 도미노처럼 연쇄적으로 우르르 무너지는 거 겨우 막는 느낌이 드는 건 왜일까.

로그인 성공했을 때 토큰만 받는게 아니라 닉네임도 응답 받아서

세션스토리지 이런데다가 저장해놓고 변경하고 꺼내쓰는 방식으로 했었어야 했을 것 같다. 

(라고... 잠시 생각했었지만, 중요한 로직이 닉네임을 가지고 동작하고 있으므로 세션 스토리지에 저장할 경우 닉네임을 사용자가 조작을 할 수 있다.)

 

현재는 로그인 성공하면 토큰만 받아올 수 있어서 토큰을 디코드하는 방법을 선택했었는데 연쇄작용이 발생할 줄은 몰랐지.. 깔깔깔

지금은 로직을 수정하려면 거의 다 갈아야 하는데 팀원들과 목요일까지 마치기로 예정되어있으므로 로직 변경은 차후 과제로 진행해보도록 하자.

 

어쨌거나..

이미 그 강을 건너버렸으니 리프레시 토큰을 사용하여 액세스 토큰을 재발급 받는 로직을 작성해보고자 한다.

 

아 테스트하다가 이상한 글 생성해버림

닉네임을 변경했을 때 게시글 닉네임도 함께 변경된 것은 확인했는데

문제는 작성된 글에 엄청난 버그와 함께 응답 코드 500에러가 발생했다.

 

무언가 잘못된게 분명해......저 글은 DB에서 밖에 못 지우는 글이 되버렸다.......

 

일단 토큰 재발급을 받으면 액세스 토큰과 리프레시 토큰이 모두 재발급된다.

따라서 세션스토리지에 새 액세스 토큰과 암호화한 리프레시 토큰을 다시 저장해야했다.

 

그리고 새로 액세스 토큰을 발급받은 유저의 글 모두 500에러가 발생했다.

어디서부터 잘못된 것인가. 내가 해결할 수 있는건가..ㅋㅋㅋ 서버에서 해주셔야 하는건가....?ㅋㅋㅋㅋㅋㅠㅠ

내 잘못인가..?...... 깔깔...

 

다른 계정을 새로 만들어서 확인해보니 내 잘못인걸로..!

중간에 테스트하면서 토큰을 갱신 안 한채로 글을 작성했던 것부터 꼬였는지 다른 계정은 멀쩡하게 동작했다.

 

아무튼 다행이다.

디버깅 어디서부터 해야하는거지 막막했는데..

 

망가진 내 첫 번째 계정은 DB를 다시 지워달라고 부탁해야겠다. ㅋㅋㅋㅋ

리프레시 토큰 테스트한다고 막 갈겨버린 코드도 리팩토링해야하고 아직도 할 게 많네..

 

일단 리프레시 토큰 사용에 대한 의문은 어느정도 해소되어서 다행이다.

이제 진짜진짜 핵심인 액세스 토큰이 만료됐을 때 새 액세스 토큰을 발급받는 로직이 들어가야 한다.

라고 생각하고 테스트 중에 새로운 버그를 발견했다.. 또르르르르

 

닉네임이나 사진만 변경했을 때는 문제가 없는데 게시글을 발행 후 닉네임을 변경했다면 프로필 사진이 날아간다.

이게 도대체 무슨 일인가 하고 콘솔을 확인해보니 CORS 때문이었다..! 이상하게도 잠깐 발생하고 나타나지 않았다. (뭔데)

 

다음은 500 코드를 반환받아서 이미지가 아예 엑박으로 바뀌는 문제다.

CORS 뜨는 거 확인하려고 했는데 계속 액세스 토큰 재발급받아서 그런건지, 이미지를 파일을 계속 보내서 그런건지는 몰라도 어느 시점에서 에러코드 500이 반환된다. 그리고 다시 확인해보면 이미지가 아예 날아가있다. 으음.

일단 잠시 제쳐두고 다른 부분부터 먼저 해결해보도록 하자..😢😢😢

 

다시 본론으로..!!


💙 액세스 토큰 재발급 받기

리프레시 토큰도 만료됐다면 404 코드,

액세스 토큰과 리프레시 토큰이 메모리에서 조회한 토큰과 다를 경우 400을 반환해주신다고 했다.

액세스 토큰 만료도 400이라 하셔서 액세스/리프레시 토큰이 일치하지 않았을 때와 어떻게 구분할지 약간 의문이 드는데 리프레시 토큰 관련 로직을 아직 제대로 확인 못해봐서 이건 조금 더 봐야할 것 같다. (401인 줄 알았는데 400이라고 하셨다..!)

 

아무튼, 액세스 토큰의 남은 유효시간이 5분미만일 때 새 액세스 토큰으로 발급받도록 할 생각이다.

테스트 해보고 싶은데 액세스 토큰이 55분이 될 때까지 기다릴 수도 없곸ㅋㅋ 

우선 관련하여 작성한 코드는 다음과 같다.

아직 만료됐을 때 확인 못해봐서 오류가 날 수도 있음.

일단 다른 부분 먼저 구현하고 뭔가 이상하다 싶으면 고쳐볼 예정.

리팩토링은 로직 구현완료 후 진행할 생각이다.

 

404 뜨는대로 다 홈으로 강제 라우트 시켰더니

다른데서도 404 에러를 반환받으면 홈으로 가져서 해당 로직은 수정중이다. 😢😢