ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [회고록] 프로젝트 기획: 기술 선택(3; Redis 도입기)
    카테고리 없음 2024. 12. 2. 21:19

     

    Refresh Token을 어떻게 저장할지 팀원들과 나눈 회의를 이어 써보겠다.

     

    우선 Access Token 과 Refresh Token 이란 무엇인지 작성해보겠다.

     

    Access Token은 사원증 같은 거다.

    회사를 들어가기 위해선 사원증을 찍고 들어가야 한다.

    즉, (인증을 필요로 하는 api 에선) Access Token 이 없으면 접근할 수 없다.

    따라서 로그인을 한 후 발급받은 Access Token을 통해 인증이 필요한 API에 접근한다.

    이때, 이 Access Token은 stateless 하기 때문에, 토큰이 탈취되어도 서버에서는 어떠한 동작을 할 수 없다.

    서버는 토큰이 정상적인 서명과 구조만 잘 갖고 있다면 정상적인 요청으로 간주한다.

    이러한 단점을 보완하기 위해 Access Token의 유효기간을 짧게 설정하여, 토큰이 탈취되어도 피해를 예방할 수 있게 한다.

    그러나 유효기간이 짧다는 말은 로그인을 자주 다시 해줘야 한다는 의미이고 이는 사용자 경험이 저하된다는 의미이다.

     

    이때 나온 것이 Refresh Token이다.

    Refresh Token은 Access Token 에 비해 긴 유효기간을 갖고 있다.

    만약 클라이언트가 만료된 Access Token과 함께 서버에 요청을 한다면, 서버는 해당 Access Token 을 보고

    '야~ 너 Access Token 만료됐어' 하고 알려준다.

    그럼 클라이언트는 다시 서버에게 '어? 그럼 나 Access Token 다시 만들어줘' 하고 요청한다. 이때, Refresh Token 이 사용된다.

    클라이언트는 Refresh Token 을 갖고 서버에게 요청하면, 서버는 이 Refresh Token 을 검증한 후, 검증이 완료되면 새로 발급된 Access Token 을 응답으로 보내준다.

    이렇게 사용자는 서버에서 보내주는 재발급된 Access Token 으로 다시 로그인할 필요 없이 서비스를 사용할 수 있다.

     

    그렇다면 이렇게 생각할 수도 있다.

    아니, Refresh Token 도 어쨋든 같은 토큰인데, 얘는 탈취 안돼?

    우선 탈취야 당연히 될 수 있다.

     

    그러나 왜 Refresh Token 이 Access Token 보다 탈취 위험이 적은지 알아보겠다.

    1. 우선 두 토큰의 전송 빈도가 다르다. Access Token은 인증이 필요한 API에는 매 요청마다 함께 보내야 한다.
    그러나 Refresh Token은 Access Token 재발급 시에만 서버로 보내기 때문에 Access Token 에 비해 전송 빈도가 낮다.

    2. 저장 위치가 다르다. Access Token은 주로 클라이언트 측 로컬 스토리지나 세션 스토리지에 저장된다. (XSS; Cross-Site Scripting에 매우 취약하다. -> 게시글 같은 곳에 javascript 악성 코드를 작성하는 행위..)

    Refresh Token은 일반적으로 HttpOnly & Secure Cookie 에 저장된다. (XSS 공격을 예방할 수 있다. 그러나 이 또한 CSRF(Cross-Site Request Forgery)를 사용하면 탈취할 수 있다.

    (※ HttpOnly Cookie: JavaScript를 통해 쿠키에 접근할 수 없게 되어, 악성 스크립트를 통해 쿠키 값에 접근하는 것을 막아준다.

    ※ Secure Cookie: HTTPS 프로토콜을 사용하여 데이터를 암호화하여 서버에 넘겨주게 되면, 해커들이 쿠키를 탈취해도 암호화가 되어있어 정보를 알아낼 수 없다.)

    3. 일반적으로 Refresh Token은 서버 쪽 데이터베이스에 저장되어 있기 때문에 탈취 후에도 서버에서 Refresh Token의 유효성 검증이 가능하여 위험이 완화된다.

     

    여기 3번을 보면, 'Refresh Token이 서버 쪽 db에 저장이 된다고? 그럼 stateless 하지 않는데?' 라는 생각이 들 수 있다.

    맞다! stateful 하다.

    여기선 보안을 강화할 것인지, stateless한 설계를 가져갈 것인지에 대한 절충안을 찾아야 한다.

    주로 사용되는 방안이 Refresh Token Rotation 이다.

    Refresh Token Rotation(RTR)의 흐름은 이렇다.

    1. 클라이언트는 Refresh Token과 함께 서버에 Access Token 재발급을 요청한다.

    2. 서버는 Refresh Token의 유효성을 검증한 후, 새로운 Access Token 을 생성한다. 이때! Refresh Token도 새로 생성한 후 갱신한다.

    3. 서버는 새로 생성한 Access Token 과 Refresh Token 을 클라이언트에 응답한다.

     

    이 방법이 보안을 강화하면서 (그나마) stateless하게 설계한 것이다.

    (일회용 Refresh Token 이 되는 것)

     

     

    이렇게 쭉.. 알아보면서 드는 생각은

    정말 보안이 중요한 서비스라면 세션을 채택하는 게 맞는 것 같다. (아마 온라인 뱅킹 사이트도 세션을 사용하는 거로 안다)

    그러나 단순 서비스 공급 사이트라면.. 확장성이 좋고 서버 부하도 줄일 수 있는 JWT 토큰이 나을 것 같다.

     

     

    그래서. 우리 팀은

    JWT 토큰을 사용하게 되었고

     

    이 Refresh Token 을 어디에 저장할지 열띤 토론을 열었다.

    처음엔 단순히 메인 DB의 user table에 저장하면 되는 거 아닌가? 라고 생각했다.

    물론 동작이 안되는 건 아니다.

    그러나 다시 생각해보자,

    Refresh Token을 영구적으로 저장할 필요가 있는가? 예를 들어.. 서버 점검으로 서버를 잠시 껐다 켰는데 이전에 로그인 했던 유저들은 재로그인 안하게~.. 여기까지만 들어도 우리 팀은 모두 '굳이?'를 외쳤다. 그러나 Redis는 휘발성 메모리다! 서버를 끄면 그냥 모든 Refresh Token이 사라진다. (이게 어떻게 보면 보안적으로도 나은 것처럼 보이기도 한다.)

    또한. Redis는 인-메모리 방식의 db이기 때문에 빠른 조회와 처리가 가능하다.

    그리고 TimeToLive 라는 애노테이션으로 토큰의 유효기간을 설정하는 추가 로직을 작성할 필요가 없다.

    이 세 가지 이유로 우리는 Refresh Token을 Redis 에 저장하기로 했다.

     

     

     

    (추가) 사용자가 로그아웃 했으나, Access Token의 유효기간이 남아 이를 악용하는 경우

    -> Redis에 따로 Blacklist를 만들어 로그아웃한 사용자의 (유효기간이 남은) Access Token을 넣어놔서 이를 방지할 수 있다.

     

Designed by Tistory.