본문 바로가기
공부/Spring

[Spring] 스프링 부트 Redis를 사용하여 refresh token 저장하기 (1)

by 웅대 2023. 7. 12.
728x90
반응형

지금까지 인증 및 인가를 구현할 때 Access token만 사용했었는데 Access token의 한계를 느껴서 Refresh token을 적용해보려고 한다.

Access token

 

우선 Access token을 사용할 때의 단점은 다음과 같다.

 

1. 토큰이 만료될 때마다 다시 로그인을 해야 한다.

access token을 발급할 때 유효 기간을 정해서 발급을 하게 되는데 이 유효 기간이 지날 때마다 다시 로그인을 해야 한다.

 

그런데 이 유효 기간이 만약 사용자가 서비스를 사용 중일 때 만료가 된다면 사용자 입장에서는 매우 불편할 것이다.

 

예를 들어 메모 서비스를 이용한다고 했을 때 열심히 메모를 작성하던 중에 토큰이 만료된다면 메모를 등록하는 시점에 재로그인을 해야 할테고 주기적으로 메모를 저장하는 로직을 따로 구현하지 않았다면 해당 정보를 다시 입력해야 할 것이다.

 

사용자의 이러한 불편함을 없애야 한다.

 

이를 해결하기 위한 간단한 방법으로는 access token의 유효 기간을 늘리는 것이다.

 

하지만 이는 다음과 같은 위험이 존재한다.

2. 토큰의 유효 기간이 길다면 탈취당했을 때 위험하다.

토큰의 유효 기간을 길게 설정한다면 해당 토큰을 누군가에게 탈취당했을 때 긴 기간 동안 사용자인 척 요청을 할 수 있게 된다.

 

그래서 access token의 유효 기간을 짧게 해서 탈취당했을 때 위험을 줄여야 한다.

 

그런데 access token의 유효 기간을 짧게 한다면 1번 문제가 다시 발생한다.

 

결국 access token 하나만으로는 이러한 문제를 모두 해결하기가 어렵다.

 

이 때 refresh token을 도입한다면 이러한 문제를 해결할 수 있다.

Access token + Refresh token

access token 재발급을 위한 refresh token을 적용한다.

access token이 사용자를 인증할 때 사용한다면 refresh token은 access token을 재발급 받을 때 사용한다.

 

우선 access token이 탈취 당했을 때 위험을 줄이기 위해 access token을 짧게 설정하고 (1시간) refresh token을 길게 설정한다. (2주)

 

기본적으로 access token을 통해 인증을 진행하고 만약 access token이 만료되었다면 refresh token을 보내서 access token을 다시 발급받아서 그 access token으로 다시 인증을 진행하면 된다.

 

과정은 다음과 같다.

  1. 유저가 로그인을 하면 서버에서 access token과 refresh token을 발급해준다.
  2. 유저는 access token과 refresh token을 안전한 저장소에 보관해둔다.
  3. 유저는 인증이 필요한 작업을 할 때 access token을 서버에게 보낸다.
  4. 서버는 access token의 유효성을 검증한 뒤 유효하다면 작업을 허용한다.
  5. access token이 만료되었다면 서버는 access token이 만료되었다는 응답을 내려준다.
  6. 유저는 refresh token을 서버에게 보낸다.
  7. refresh token이 유효하다면 새로운 access token을 발급해주고 유저는 이 access token으로 다시 작업을 요청한다.
  8. refresh token이 유효하지 않다면 다시 로그인을 하도록 응답을 내려준다.

access token을 사용할 때의 문제점을 어느 정도 해결하였다.

 

하지만 여전히 문제들은 남아 있다.

 

refresh token을 탈취당하면 탈취자는 무제한으로 access token을 발급할 수 있다.

access token은 유효 기간이 짧기 때문에 탈취당하더라도 오랫동안 사용하지 못한다.

 

하지만 refresh tocken을 탈취당한다면 refresh token이 만료되기 전까지 이 refresh token으로 계속 access token을 발급해낼 수 있다.

 

심지어 refresh token의 유효 기간을 길게 설정했기 때문에 오랜 기간 동안 사용자인 척 요청을 할 수 있다.

 

이를 해결하기 위한 방법으로는 RTR(Refresh Token Rotation)을 사용할 수가 있다.

 

RTR을 위해서 서버가 refresh token을 발급할 때 데이터베이스에 refresh token을 저장해둔다.

 

그리고 유저가 refresh token을 통해서 access token을 재발급 받을 때 refresh token도 재발급해서 같이 넘겨주는 것이다.

 

또한 재발급 받은 refresh token을 데이터베이스에 갱신한다.

 

그리고 유저가 access token 재발급을 요청하기 위해서 서버에게 refresh token을 보내면 해당 서버는 유효성 검증을 하고 추가로 데이터베이스에 저장된 refresh token과 동일한지 확인한다.

즉, access token을 재발급 할 때 refresh token도 재발급 하는 것이다.

refresh token rotation을 적용했기 때문에 refresh token의 유효 기간이 길더라도 값이 계속 갱신되기 때문에 긴 유효 기간의 문제점을 해결할 수 있다.

 

이제 탈취자가 refresh token을 탈취했다고 가정하자.

 

refresh token을 탈취 당한 상태에서 유저가 계속 인증이 필요한 요청을 보내고 있다고 하자.

 

그러면 access token이 만료될 때마다 계속 refresh token이 갱신되고 탈취자가 access token을 재발급 받기 위해 옛날 refresh token을 보내게 될 것이다.

 

이 때 데이터베이스에 저장된 refresh token과 다르기 때문에 악의적인 공격으로 간주하고 refresh token을 데이터베이스에서 삭제하고 다시 로그인을 하라는 응답을 주는 것이다.

 

만약 탈취자가 refresh token을 발급받고 먼저 access token을 재발급받으면 어떻게 될까?

 

데이터베이스에 refresh token이 갱신되고 진짜 유저는 옛날 refresh token을 가지고 있을 것이다.

 

이 때 진짜 유저가 다시 refresh token을 보내면 데이터베이스와 다르기 때문에 악의적인 공격으로 간주하고 refresh token을 데이터베이스에서 삭제하고 다시 로그인을 하라는 응답을 준다.

 

진짜 유저가 다시 로그인을 해야 하는 불편함이 있지만 보안적으로는 좋은 방법이다.

 

과정은 다음과 같다.

  1. 유저가 로그인을 하면 서버에서 access token과 refresh token을 발급하고 refresh token을 데이터베이스에 저장해둔다.
  2. 유저는 access token과 refresh token을 안전한 저장소에 보관해둔다.
  3. 유저는 인증이 필요한 작업을 할 때 access token을 서버에게 보낸다.
  4. 서버는 access token의 유효성을 검증한 뒤 유효하다면 작업을 허용한다.
  5. access token이 만료되었다면 서버는 access token이 만료되었다는 응답을 내려준다.
  6. 유저는 refresh token을 서버에게 보낸다.
  7. refresh token의 유효성을 검증하고 데이터베이스에 저장된 refresh token과 동일한지 확인한다.
  8. 유효하고 동일하다면 access token을 발급해준다.
  9. 유효하지 않거나 동일하지 않다면 refresh token을 삭제하고 다시 로그인을 하도록 응답을 내려준다.

여러 문제점들을 해결했지만 역시나 완벽하지는 않다.

 

만약 access token과 refresh token을 갱신하는 시점에 둘 다 탈취당하고 진짜 유저가 더 이상 서비스를 이용하지 않는다고 하자.

 

진짜 유저가 서비스를 더 이상 이용하지 않는다면 데이터베이스의 refresh token을 갱신하지 않기 때문에 탈취자가 계속 refresh token을 통해 access token을 발급받을 수 있을 것이다.

 

유저가 계속 서비스를 사용 중일 때는 refresh token이 갱신되기 때문에 위험을 줄일 수 있으나 더 이상 서비스를 사용하지 않는다면 여전히 위험은 존재한다.

 

refresh token을 통해 token 인증 방식의 위험을 줄이고 애초에 토큰을 탈취 당하지 않는 방법을 추가로 적용하면 좋을 것 같다.

 

Redis

 

그렇다면 서버는 refresh token을 어디에 보관하는 것이 좋을까?

 

가장 쉬운 방법은 데이터베이스 유저 테이블에 column을 새로 만들어서 보관하는 것이지만 이번 포스팅에서는 한 번 Redis를 사용해보려고 한다.

 

Redis는 Remote Dictionary Server의 약자로 메모리에서 동작하는 비관계형 데이터베이스이다.

 

메모리에서 동작하기 때문에 디스크에서 동작하는 데이터베이스보다 속도가 빠르다.

 

또한 비관계형 데이터베이스이기 때문에 key - value를 기반으로 데이터를 저장한다.

 

이 Redis에 refresh token을 보관해서 서버의 부담을 줄이고 refresh token 조회를 빠르게 해보려고 한다.

 

key에는 유저 pk를 넣고 value에는 refresh token을 넣으려고 한다.

 

다음은 구현이다.

Redis 도커로 띄우기

먼저 Redis를 메모리에 띄워야 하는데 Redis를 직접 설치해도 되지만 나는 도커를 통해서 간단하게 Redis를 띄워보려고 한다.

 

<도커 간단 사용법>

https://growth-coder.tistory.com/216

 

[Docker] 도커 개념 및 사용법

프로젝트를 진행하면서 로컬에서 잘 작동하던 프로젝트가 서버에 배포하면 잘 작동하지 않을 수 있다. 수많은 원인이 존재하겠지만 라이브러리 버전이 다르다거나 네트워크 환경이 다르다거나

growth-coder.tistory.com

 

가장 먼저 도커 허브에서 redis를 검색하여 official image의 가장 최신 버전 이미지를 검색한다.

해당 이미지를 로컬로 pull 한다.

docker pull redis

해당 이미지의 호스트 포트 번호와 컨테이너 포트 번호를 매핑해주고 백그라운드로 실행한다. (Redis는 기본 포트가 6379이다.)

docker run -p 6379:6379 -d redis

Redis에서 cli로 명령어를 줘서 테스트를 해보자.

 

docker exec -it [컨테이너 아이디] redis-cli

간단한 명령어들을 테스트 해보자.

 

모든 key 가져오기 
keys *

key - value 저장하기
set key value

key에 해당하는 value 가져오기
get key

key에 해당하는 value 삭제하기
del key

물론 스프링부트로 접근할 예정이라 cli를 사용하지는 않을 것이다.

 

cli 명령어들이 성공적으로 작동한다면 Redis를 메모리에 띄우는 것에 성공했다.

 

이 Redis에 refresh token을 저장하면 된다.

 

다음 포스팅에서 스프링부트와 Redis를 연동해보려고 한다.

참고

https://velog.io/@hhs7425/JWT%EB%A1%9C-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

https://junior-datalist.tistory.com/352

 

 

 

728x90
반응형

댓글