Refresh Token 을 사용하는 이유
- Access Token 만을 통한 인증 방식의 문제는 만일 제 3자에게 탈취당할 경우 보안에 취약하다는 점이다. Access Token은 발급된 이후, 서버에 저장되지 않고 토큰 자체로 검증을 하며 사용자 권한을 인증하기 때문에, Access Token이 탈취되면 토큰이 만료되기 전 까지, 토큰을 획득한 사람은 누구나 권한 접근이 가능해 지기 때문이다.
- JWT는 발급한 후 삭제가 불가능하기 때문에, 접근에 관여하는 토큰에 유효시간을 부여하는 식으로 탈취 문제에 대해 대응을 하여야 한다.
- 이처럼 토큰 유효기간을 짧게하면 토큰 남용을 방지하는 것이 해결책이 될 수 있지만, 유효기간이 짧은 Token의 경우 그만큼 사용자는 로그인을 자주 해서 새롭게 Token을 발급받아야 하므로 불편하다는 단점이 있다. 그렇다고 무턱대고 유효기간을 늘리자면, 토큰을 탈취당했을 때 보안에 더 취약해지게 된다.
- 이때 “그러면 유효기간을 짧게 하면서 좋은 방법이 있지는 않을까?”라는 질문의 답이 바로 Refresh Token이다. 이름이 다르지만 형태 자체는 Refresh Token은 Access Token과 똑같은 JWT다.
- 단지 Access Token은 접근에 관여하는 토큰이고, Refresh Token은 재발급에 관여하는 토큰 이므로 행하는 역할이 다르다고 보면 된다.
Access Token과 Refresh Token의 사용방법과 흐름
- 처음에 로그인을 했을 때, 서버는 로그인을 성공시키면서 클라이언트에게 Access Token과 Refresh Token을 동시에 발급한다.
- 서버는 데이터베이스에 Refresh Token을 저장하고, 클라이언트는 Access Token과 Refresh Token을 쿠키, 세션 혹은 웹스토리지에 저장하고 요청이 있을때마다 이 둘을 헤더에 담아서 보낸다.
- 이 Refresh Token은 긴 유효기간을 가지면서, Access Token이 만료됐을 때 새로 재발급해주는 열쇠가 된다.
- 따라서 만일 만료된 Access Token을 서버에 보내면, 서버는 같이 보내진 Refresh Token을 DB에 있는 것과 비교해서 일치하면 다시 Access Token을 재발급하는 원리이다.
- 그리고 사용자가 로그아웃을 하면 저장소에서 Refresh Token을 삭제하여 사용이 불가능하도록 하고 새로 로그인하면 서버에서 다시 재발급해서 DB에 저장한다.
Access / Refresh Token 재발급 원리
1. 기본적으로 로그인 같은 과정을 하면 Access Token과 Refresh Token을 모두 발급한다.
이때, Refresh Token만 서버측의 DB에 저장하며, Refresh Token과 Access Token을 쿠키 혹은 웹스토리지에 저장한다.
2. 사용자가 인증이 필요한 API에 접근하고자 하면, 가장 먼저 토큰을 검사한다.
이때, 토큰을 검사함과 동시에 각 경우에 대해서 토큰의 유효기간을 확인하여 재발급 여부를 결정한다.
- case1 : access token과 refresh token 모두가 만료된 경우 → 에러 발생 (재 로그인하여 둘다 새로 발급)
- case2 : access token은 만료됐지만, refresh token은 유효한 경우 → refresh token을 검증하여 access token 재발급
- case3 : access token은 유효하지만, refresh token은 만료된 경우 → access token을 검증하여 refresh token 재발급
- case4 : access token과 refresh token 모두가 유효한 경우 → 정상 처리
3. 로그아웃을 하면 Access Token과 Refresh Token을 모두 만료시킨다.
Refresh Token 을 Redis 에서 사용하고 있는데 시간이 만료되었을 때 바로바로 제거가 되게끔 처리가 되려면 ?
- @RedisHash(value = "refreshToken", timeToLive = 초단위시간)
- 위와 같이 일정 기간을 정해서 시간이 다했을때 자동으로 삭제되도록 할 예정이고, 또한 로그아웃 API가 완성되면 로그아웃시에도 토큰이 삭제되도록 할 예정입니다.
Access Token과 Refresh Token을 어떻게 관리할지
1. 서버단에서는 리프레시 토큰을 Redis에 저장한다.
- Redis에 저장하는 이유는 아래와 같다
- 빠른 액세스 속도로 사용자 로그인시 (리프레시 토큰 발급시) 병목이 되지 않는다.
- 또한 리프레시 토큰은 발급된 후 일정 시간 이후 만료되어야 한다. 리프레시 토큰을 RDB등에 저장하면, 스케줄러등을 사용하여 주기적으로 만료된 토큰을 만료 처리하거나 제거해야한다. 하지만, 레디스는 기본적으로 데이터의 유효기간(time to live)을 지정할 수 있다. 이런 특징들은 리프레시 토큰을 저장하기에 적합하다.
- @RedisHash(value = "refreshToken", timeToLive = 초단위시간)
- 물론 JWT와 같은 클레임 기반 토큰을 사용하면 리프레시 토큰을 서버에 저장할 필요가 없다. 하지만, 사용자 강제 로그아웃 기능, 유저 차단, 토큰 탈취시 대응을 해야한다는 가정으로 서버에서 리프레시 토큰을 저장하도록 구현하였다.
2. 프론트 단에서는 두가지의 경우가 있다. 어떤게 좋은지는 잘 모르겠다.
- 쿠키에 보관
- localStorage 보관
localStorage에 저장하는 경우
- cookie의 httpOnly 옵션도 XSS 공격을 완벽히 막을 수 없다. 어차피 XSS 방어는 필수적이므로 cookie의 장점이 매력적이게 보이지 않는다.
- cookie를 사용한다면 백엔드 api에 내가 사용하는 cookie를 위한 설정을 요구해야한다. 백엔드와 조율이 잘 되는 상태면 cookie를 사용해도 문제 없지만 서드파티 api의 경우 거의 불가능하므로 localStorage가 더 좋을 것 같다.
- mdn은 저장소로 쿠키를 추천하지 않는다. 대신 ModernStorage(localStorage와 sessionStorage)를 추천한다.
cookie를 지지하는 경우
- CSRF 공격은 다루기 쉬운 반면 프론트엔드 크기가 크면 클수록 XSS 공격을 막기위한 작업은 많아지므로 쿠키 사용을 추천
- 쿠키는 별도로 헤더에 담지 않아도 요청을 보낼때 자동으로 담아서 보내지기 때문에 매번 서버로의 요청에 담아야하는 토큰이라는 성격과 잘 맞고, 코드도 더 간결하게 작성할 수 있다
Refresh Token의 한계점
리프레시토큰을 도입해서 액세스토큰의 공격범위를 줄인다고해도, 리프레시토큰마저 탈취당하면 도로묵이고.
그래서 JWT로그인을 도입할 때는 이 한계점이 명확하다는것을 유의해서 작업해야 할 것 같다.
또다른 큰 단점은, 임의적으로 생성된 JWT를 서버에서 임의적으로 삭제할 수 없다는 점이다.
사실 이 점이 더 큰 단점이라고한다.
만약 리프레시토큰이 탈취당한다면 어떤 액션을 취해야할지?
아니면 클라이언트측이 갖고있는 토큰을 아예 탈취도 못하도록 보안에 더 신경쓰거나.
아니면 탈취당했더라도 금새 리프레시토큰을 재발급할 수 있게 한다거나?
아무리 생각해봐도 뭐라도 하나 털린순간 피해를 당하긴 하는데,얼마나 줄이냐의 문제같다.
[ 참고 자료 ]
'항해99 개발 일지 > [Final] 실전 프로젝트' 카테고리의 다른 글
[19] OAuth 2.0 개념 정리 (0) | 2023.01.24 |
---|---|
[18] 3주차 중간 발표 피드백 정리 (0) | 2023.01.22 |
[16] Docker 실행 / 명령어 정리 (0) | 2023.01.19 |
[15] Spring CI/CD (1) 개념 (0) | 2023.01.18 |
[14] 2주차 기술멘토링 피드백 정리 (0) | 2023.01.18 |