JWT 어떻게 써야 잘 써먹었다고 할 수 있을까.

공부는 혼자하는 거·2024년 5월 28일
0

잡담

목록 보기
14/14

일단 기본개념 살짝

기본적으로 HTTP는 무상태성 특성으로 연결이 해제됨과 동시에 서버와 클라이언트는 클라이언트가 이전에 요청한 결과에 대해 잊어버리게 된다. 따라서 요청을 할 때마다 서버에 연결을 해야한다. HTTP의 이러한 특성으로 인해 웹사이트는 상태를 유지해야 하는 작업들을 처리하기 위해 여러가지 다른 방안을 써먹어야되는데, 대표적으로 자동 로그인 같은 기능을 위한 크게 2가지 방식인 JWT와 세션을 비교해보겠다.

Session

클라이언트가 서버에 접속되어 요청을 한다. 각각의 클라이언트(브라우저)에 임의의 값을 부여해서 서버가 갖고 있다. 이후에 들어오는 요청에 대해 서버가 갖고있는 클라이언트의 정보와 비교를 해서 동일한 클라이언트인지를 판별하게 되는데 쿠키와 반대로 특정 클라이언트의 정보는 서버에 저장이되어있다. 첫 요청 발생시 서버에서는 ID를 생성. 그런 후 쿠키가 저장되는 방식과 마찬가지로 클라이언트에 ID만이 저장. 이후의 요청이 발생되었던 ID만이 서버로 보내는 요청에 포함이 된다. 이 ID정보를 읽어서 서버는 이전에 있는 값을 바꾸거나 새로운 값을 적을 수 있다. 다만 클라에서는 ID에 붙이는 이름과 ID의 실제 값만을 저장하고 있고 나머지 값은 서버에 저장이 된다. session을 사용한다고 해서 cookie를 안쓰는 것은 아니다. 다만 cookie에 중요 정보를 넣지 않았기 때문에 탈취 되더라도 해석해도 의미없는 문자열인 세션 Id가 들어있다.

  1. 클라이언트가 서버에게 login 요청을 한다.
  2. 서버에서 login 인증을 하고 올바르면 세션 객체를 생성하고 세션 Id를 cookie를 통해 클라이언트에게 전달한다.
  3. 세션 객체는 서버에 저장해놓는다.
  4. 클라이언트가 서버에 작업을 요청할 때 요청 헤더에 세션 Id가 같이 전달된다 (쿠키이므로 자동으로 붙어나감)
  5. 서버에서는 받은 요청헤더에 세션 Id를 확인해서 세션 객체를 검색하여 있으면 인증 된 것으로 판단된다.

장점

쿠키방식과 동일하지만 쿠키에 아무런 의미가 없는 세션 Id가 저장이 되므로 탈취되더라도 해석할 수 없다.

단점

  1. 쿠키는 쉽게 탈취될 수 있다. 중간에 가로채서 훔친 쿠키로 대신 요청을 보낼 수 있는 세션 하이재킹 공격을 당할 수 있다.
    (1) HTTPS를 사용해 요청 자체를 탈취해도 안의 정보를 읽기 힘들게 한다.
    (2) 세션에 유효시간을 넣어 유효시간이 끝나면 더이상 해커가 이용할 수 없게 한다.

  2. 서버에 세션 객체를 저장해놓기 때문에, 사용자가 다수일 경우 서버의 자원을 많이 소모시킴

  3. 확장성이 좋지 않다. 기본적으로 세션 객체는 각 서버 프로세스 메모리 공간에 저장되기 때문에, 스케일 아웃시 모든 서버가 세션을 공유할 수 있도록 별도의 중앙 세션 관리 시스템이 필요하다. 따라서 Session Clustering, Session Storage, Sticky Session 등 다른 방안이 필요하다. 또한 CORS 문제와 관련해서도 확장이 어려운데, 웹앱에서 세션을 관리할 때 자주 사용되는 쿠키는 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어 있다. 따라서 쿠키를 여러 도메인에서 관리하는것은 번거롭다.

JWT

세션의 단점을 어느정도 해소할 수 있는 게 JWT 인증 방식이다. JSON Web Token의 약자로 heaer, payload, signature로 구성 된 문자열이다. 기본적으로는 아래의 플로우를 따른다.

  1. 클라이언트가 서버에게 login 요청을 한다.
  2. 서버에서 login 인증을 하고 올바르면 서버의 SecretKey를 통해 Token을 생성하고 Token을 클라에게 담아서 보낸다.
  3. 서버에서 응답한 Token을 클라이언트에서 저장하고, 인증/인가 요청 시마다 서버로 요청한다.
  4. 서버에서는 받은 Token을 검증하여 인증 여부를 판단

장점

  1. JWT 자체로 유효성 여부를 판단할 수 있기 때문에 이를 저장하기 위한 장소를 따로 필요로 하지 않는다. 때문에 서버측 부하를 낮출 수 있고 독립적이기 때문에 능률적으로 접근 권한관리를 할 수 있고 분산/클라우드 기반 인프라 스트럭처에도 잘 대응할 수 있다.

  2. 암호화한 시그니쳐를 통해 위변조를 쉽게 알 수 있기 때문에 쿠키와 달리 보안성이 좋다.

단점

  1. 위변조가 어렵다 뿐이지, 헤더와 페이로드 부분은 별다른 암호화 없이 그냥 Base64 방식으로 인코딩 되어있어서 누구나 까볼 수 있다. 애초에 암호화의 목적이 아니라 인증이 목적이다. 그래서 보통 민감한 정보는 넣지 않는다.

  2. 서버는 토큰을 만들고 검증만 할 뿐이지, 따로 토큰의 상태를 관리를 안 하기 때문에 토큰을 누군가에게 도둑맞았을 경우에 대응이 어렵다. 전체 토큰을 무효화하는 등, 세션 같이 세밀한 제어가 불가능하다. 같은 이유에서 한번 발급된 Token은 수정, 폐기가 불가하다.

Refresh Token

JWT를 쓰면서도 이러한 한계들을 극복하기 위해 여러가지 다른 방안들이 튀어나왔는데, 그 중 하나가 Refresh Token을 활용하는 거다. JWT의 단점 중 하나가 기본적으로 무상태성 인증 프로토콜이기 때문에, 한번 만들고 나면 서버에서 수정, 폐기가 불가능하기 때문에 탈취당하면, 대응이 안 된다는 점인데, 이를 보완하기 위해 Access token의 만료시간을 짧게 가지고 Refresh token을 이용해서 중간중간 Access token을 재발급하게 해서 Access token이 탈취되더라도 이용할 수 있는 시간을 최소화하는 매커니즘이다.

  1. 클라는 로그인이 성공되면 서버에서 두 가지 유형의 JWT를 응답받는다.

  2. Access token은 짧은 수명 주기를 가지고, 서버로부터 승인을 받아 인증이 필요한 모든 HTTP request에 포함된다.

  3. Refresh token은 긴 수명 주기를 가지고, access token이 만료되었을 때 새로운 access token을 발급해주는 토큰이다.

보통 Refresh Token 같은 경우, 만약 탈취당하더라도 서버 측에서 조치를 취할 수 있도록, 별도의 DB 공간에 상태를 저장하도록 설계한다. 그런데 이렇게 하면, JWT 인증 방식의 장점인 저장소를 따로 두지 않아 서버측 부하를 낮출 수 있다는 점이 유명무실해지게 되긴 한다.

클라이언트는 AccessToken, RefreshToken을 어디에다 저장하지?

웹 어플리케이션의 경우, 일반적으로 저장소로 활용될 수 있는 부분은 아래와 같이 열거할 수 있다.

1. 쿠키
2. 로컬스토리지
3. 세션스토리지
4. 메모리

메모리에 변수로 저장하는 것이 보안상 가장 안전하긴 하겠지만, 브라우저 새로고침이나, 종료시와 같은 상황에도 상태를 유지할 수 있을려면 다른 3가지 영역에 저장하는 것이 여러모로 멘탈에 좋다. 주로 LocalStorage 또는 쿠키에 저장하는 방식을 쓰는데, 각각의 장단점을 살펴보겠다.

LocalStorage

  • 편리하다
  • XSS 공격에 취약하다.

XSS 공격은 웹사이트에서 공격자가 JavaScript를 실행할 수 있을때 발생한다. LocalStorage는 JavaScript로 쉽게 접근가능하다.

  • 쿠키는 localStorage만큼 XSS 공격에 취약하지 않다. HttpOnly 쿠키 (클라이언트 대신 서버에서 생성) 등 어느정도 대응이 가능하다.
  • 저장공간이 LocalStorage 보다는 빡빡한 편이다.
  • CSRF 공격에 취약하다.

sameSite 플래그와 anti-CSRF tokens를 사용한다면 CSRF 공격으로부터 어느정도 예방시킬 수 있다. Auth 및 API 서버가 서로 다른 도메인에서 호스팅되는 경우 솔루션이 아닐 수 있다. CSURF같은 라이브러리를 사용함도 대응방안이다.

개인적인 추천방식

나 같은 경우, AccessToken은 LocalStorage에 보관하고, 클라이언트는 서버 요청마다 header에 값이 있으면 같이 전송해주도록 하고, Refresh Token은 쿠키를 통해 관리하도록 하는 게 좋다고 본다. 쿠키이므로 클라이언트는 RefreshToken의 존재를 모른 채, 서버 측에서 알아서 AccessToken이 만료되면, 쿠키를 보고 다시 재발급하도록 하고, 쿠키 옵션은 HTTP Only 쿠키를 사용해 자바스크립트 기반 공격을 방어하고 프론트와 서버간 호스트가 다를 수도 있으므로, SameSite None으로 설정해줄 필요성도 있을 것이다. 그렇게 되면 Secure 옵션을 키도록 강제되는데 (https). 로컬에서 개발하기에는 여러모로 불편하다. 그래도 보안상 이점이 있다.

Refresh Token Rotation

그래도 만약 2개 다 탈취당하는 것을 대비한다면? Refresh Token도 매번 Access Token 재발급하는 타이밍에 맞춰 갱신해주는 방법이 있겠다. 기존에는 RT를 이용해 AT만 재발급했지만, Refresh Token Rotation 은 AT 뿐만 아니라 RT 도 같이 재발급한다. 그러면 유효기간이 긴 Refresh Token이 탈취된 경우를 방지할 수 있지만, 만약 해커가 탈취한 Refresh Token으로 정상 유저보다 먼저 Access Token을 재발급 받는 경우 발생할 문제점이 또 있다. 이를 위해 refresh token DB의 PK를 유일무이한 다른 값으로 선정해서 고르든가 하는 방법이 있을 것이다.

보안에는 완전한 정답이 없다. 결국 어느 순간 지금 시점의 나는 이 정도 선에서 만족한다고 합의를 봐야된다. 트레이드 오프다. 보안에 집중하면, 개발이 불편해진다. 그리고 생각해보면 대부분의 보안은 과잉 걱정일 수도 있다. 실용성을 따지면서 협의점을 내 머릿속에 설정해야 된다..

참고

https://www.cyberchief.ai/2023/05/secure-jwt-token-storage.html

Refresh Token Rotation 과 Redis로 토큰 탈취 시나리오 대응

LocalStorage vs. Cookies: JWT 토큰을 안전하게 저장하기 위해 알아야할 모든것

profile
시간대비효율

0개의 댓글