Session, JWT(+ Refresh Token)

Solf·2024년 2월 25일

WEB

목록 보기
1/11

사용자 맞춤형 서비스는 현대 웹 서비스에서 흔하게 요구된다. 이때 웹 서버가 사용자를 구별하기 위한 방법이 로그인이다. 로그인부터 시작해서 Session 인증, JWT인증이 생겨난 이유와 각 인증 방식의 특징까지 고민했던 내용들을 정리해보자.

로그인

매순간 로그인을 하는 것은 굉장히 번거로운 일이다. HTTP 통신의 Stateless한 특성을 살린다면 모든 HTTP 요청마다 로그인 정보를 함께 담아서 보내야한다. 이때 ID와 Password를 매순간 담아서 보낸다면? 해커에 의해서 탈취 당하기 쉬우며, 굉장히 번거로울 것이다.

HTTP의 Stateless 특성 한계

HTTP의 Stateless 아키텍처는 서버가 클라이언트의 상태를 저장하지 않는 것을 말한다. HTTP는 이론적으로 완벽하게 Stateless하게 설계되어야 하지만 앞서 번거로운 상황을 막기 위해서 실제 웹 서비스는 여러가지 저장소를 사용할 수 있다. 브라우저 저장소는 CookieWeb Storage(Local Storage, Session Storage)를 가지며 서버 저장소로는 Session이 있다. 이런 저장소를 활용한 대표적 인증방법들이 바로 Session인증, JWT인증 방식이다.

각 저장소에 대한 특성은 다른 포스팅에서 정리하겠다.

Session 인증방식


출처: http://gcs.emro.co.kr:8090/pages/viewpage.action?pageId=9873130

Session-based authentication is a stateful authentication technique where we use sessions to keep track of the authenticated user. -roadmap.sh

JWT보다 역사적으로 오래된 인증방식이며 Django나 Spring Security에서 기본적으로 제공하는 인증방식이다.

장점

  • 보안: 서버 측에 저장되어 있어 세션 ID가 탈취되면 즉각적인 조치가 가능하다. (관리가능)

단점

  • 서버의 부하: 인증 요청마다 세션 저장소를 확인해야하기에 서버에 부하가 간다. (이를 해결하기 위해 가벼운 key value 방식의 DB redis를 사용해 부하를 최소화 하기도 한다.)
  • 스케일 아웃 어려움: 요즘 웹서버는 요청 부하가 커지면 서버의 개수를 늘리는 '스케일 아웃' 방식을 채택하는데 늘어난 서버가 세션 저장소를 공유해야하기에 관리가 까다롭다.
  • stateless 파괴: 클라이언트에 대한 세션아이디를 세션에 저장함으로서 HTTP의 stateless한 설계가 무너진다.

JWT 인증방식


https://jwt.io/

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. - jwt.io

JWT은 위와 같이 HEADER, PAYLOAD, SIGNATURE로 구성된 JSON이다. HEADER에는 토큰의 타입과 암호화 알고리즘의 종류, PAYLOAD에는 인증정보와 토큰의 유효기간, SIGNATURE에는 'HEADER.PAYLOAD'를 암호화 알고리즘에 따라 비밀키로 암호화한 값이 들어간다. 이를 BASE64방식으로 인코딩 해주면 왼쪽과 같은 JWT이 만들어진다. 구체적인 구성 정보들은 공식 사이트를 들어가보면 확인 할 수 있다.


출처: http://gcs.emro.co.kr:8090/pages/viewpage.action?pageId=9873130
JWT인증방식은 세션ID 대신 JWT을 클라이언트에 전달하며, 서버는 나중에 쿠키에 담겨온 요청을 해독하여 PAYLOAD 내부의 인증정보를 바탕으로 사용자를 식별한다. 즉 세션 저장소를 사용하지 않으며 HTTP의 stateless한 설계를 가져올 수 있는 방식으로 개발되었다.

이때 HEADER와 PAYLOAD는 암호화 없이 BASE64방식으로 인코딩만 되어있기에 해커가 쉽게 탈취 후 조작이 가능한데, 서버 측에서 SIGNATURE 부분을 비밀키로 복호화 하면 똑같은 HEADER PAYLOAD가 또 들어가 있기에 두 값이 다르면 해커에게 조작당했음을 알 수 있다.

장점

  • stateless: HTTP의 stateless한 성질을 지킬 수 있다.
  • 스케일 아웃: 세션 저장소 관리가 필요 없기에 서버의 수평확장 시 매우 편리하다.
  • 속도: 저장소를 조회하지 않기에 서버에 부하가 덜하다.

단점

  • 보안: 세션과 달리 토큰은 서버에서 관리하지 않기에 탈취 당하더라도 할 수 있는 조치가 없다.
  • 토큰 크기: 모든 인증정보를 PAYLOAD에 담기에 세션과 달리 굉장히 길어질 수 있다.

OAuth?

https://datatracker.ietf.org/doc/html/rfc6749
https://oauth.net/

OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다 - 위키백과

Refresh Token은 사실 웹 인증 표준 프로토콜 중 하나인 OAuth로부터 나온 것이다. 소셜로그인 서비스가 대부분 OAuth 표준을 지켜서 만들어졌으며, OAuth는 Access Token과 Refresh Token으로 분리되며 이런 토큰들을 활용한 인증과정에 대해서 다루고 있다. 이때 Access Token으로 구현될 수 있는 것 중 하나가 JWT이다. 또한, JWT은 이와 별개의 토큰 기반 웹 인증 표준 중 하나이다. 즉 JWT과 OAuth(Access Token & Refresh Token)은 아예 별개의 표준이며 OAuth 프로토콜을 구현함에 있어 Access Token을 JWT으로 구현할 수도 있다고 이해하면 되겠다.

Refresh Token


출처: https://is.docs.wso2.com/en/5.9.0/learn/refresh-token-grant/

기존의 JWT 인증방식은 탈취 당하면 관리가 사실상 불가능하기에 유효기간을 제한한다. 그러나 해당 유효기간을 너무 짧게 잡으면 사용자가 직접 로그인을 자주해야하며, 너무 길게하면 보안적으로 문제가 발생한다. 이런 문제를 해결 할 수 있는 것이 JWT을 다시 발급하기 위한 토큰 Refresh Token이다. 다시한번 강조하지만 Refresh Token은 웹 표준 인증 프로토콜 중 하나인 OAuth에서 등장한ㄲ 개념이다.

Refresh Token은 JWT으로 구현될 수도 있으며 랜덤한 문자열이 될 수 있으며 정해진건 없다. 이는 Access Token도 마찬가지이다. 단지 OAuth에서는 각 역할을 명시하고 있다. 그러나 일반적으로 Refresh Token은 UUID를, Access Token은 JWT을 사용한다. 위의 그림으로 흐름을 파악해보자. (OAuth에서 제공하는 RFC 문서의 Refresh Token 부분을 가져온 것이다.)

흐름

  1. 사용자가 인증을 요청한다.(아이디와 비밀번호로)
  2. 서버는 클라이언트를 인증하고 권한 부여 유효성을 검사 + 권한 제공 유효기간이 짧은 Access Token과 유효기간이 긴 Refresh token을 함께 제공한다.
  3. 사용자는 요청시 Access token을 함께 담아 보호된 리소스를 요청한다.
  4. Access token이 유효하면 서버는 원하는 자료를 준다.
  5. 3~4까지 Access token이 만료될때까지 반복 만료되면 6으로
  6. 사용자가 만료된 Access token을 보낸다.
  7. 만료된 토큰이기에 서버는 error를 반환한다.
  8. 사용자는 인증서버에 Refresh Token을 담아 인증요청
  9. 서버는 Refresh 토큰의 유효성을 검사해 유효하다면 새로운 Access 발급. 선택적으로 Refresh Token도 재발급

이렇게 구현하면 사용자 입장에서는 마치 인증이 단순히 계속 유지되는 것으로 보이지만, 보안이 취약한 Access Token의 유효기간은 최소화 할 수 있다.

Refresh Token 어떻게 구현할까?

OAuth에서는 Refresh Token이 String이며 동일하거나 그보다 적은 권한의 AccessToken을 재발급받기 위한 용도라는 것을 명시하고 있다. 따라서 구체적으로 정해진 것은 없으며 우리는 기본적으로 UUID(랜덤한 문자열)과 JWT을 고려할 수 있다.

Refresh Token - UUID로 구현하는 경우

대부분 권장하는 구현방법이다. 서버 DB에 사용자 식별자와 UUID(옵션으로 Access Token까지)를 저장해서 관리해야 하며, Redis를 자주 활용한다.

JWT + Refresh Token 해킹 시나리오
각 토큰이 탈취 당했을때 일어나는 일을 생각해보자

  1. Access Token(유효한): Access Token의 유효기간을 짧게 줌으로서 피해를 최소화할 수 있다.
  2. Refresh Token(유효한): DB에 Access Token을 함께 저장했다면 방어할 수 있다. 그렇지 않았을 경우 얼마든지 피해를 입힐 수 있다.
  3. Refresh Token(유효) + Access Token(유효): 얼마든지 피해를 입힐 수 있다.
  4. Refresh Token(유효) + Access Token(만료): 얼마든지 피해를 입힐 수 있다.

이 외에 보안 강화하는 전략으로 생각해볼만한건 아래와 같다.

  • 로그아웃시 DB의 토큰 정보 모두 삭제(Refresh Token 즉시 무효화)
  • Refresh Token을 일회성으로 강제한다.

Session 인증과의 유사성
지금까지 살펴봤다면 알겠지만 Refresh Token을 UUID를 활용해 구현하면 일종의 세션인증방식과 동일하며, 따라서 관리가 가능하지만 DB를 조회하는 시간이 걸린다는 장단점 또한 동일하게 가져온다. 즉 JWT + Refresh Token은 세션인증과 JWT인증방식의 하이브리드라고 보는게 맞을 듯 하다. 또한 Stateless한 아키텍쳐에서 오는 스케일 아웃의 편리함과 같은 장점은 아예 사라진다.

요약
장점: 세션 인증과 마찬가지로 관리가 가능하다.
단점: Stateless 설계 파괴

즉 JWT과 Session 인증의 하이브리드 느낌으로 볼 수 있다.
대신 Session 인증보다 Session 저장소 접근 횟수가 적다. (부하 감소)

Refresh Token - JWT으로 구현하는 경우

일반적으로 해당 방법은 권장하지 않는다. 만약 세션 저장소 없이 JWT과 동일하게 관리하지 않고 무상태성 JWT을 사용한다면, Refresh Token을 일회성으로 제한하기 어렵다.

그렇게 되면 단순히 수명이 긴 Access Token이 된다.

저장장소

Access Token : 로컬 스토리지
이유 : 인증이 가능하다는 특징이 있어 CSRF공격의 위험성이 있다.
또한 수명이 짧아 XSS에는 피해가 최소화된다.
자주 갱신해야하는데 HttpOnly 쿠키는 다소 관리가 번거롭다.

Refresh Token : HttpOnly 쿠키
이유 : 수명이 길어 민감한 데이터이므로 XSS로 접근 불가능 하도록 한다.
인증에는 영향이 없기 때문에 CSRF로부터 상대적으로 안전하다.

자 그래서 뭐 쓸래?

여기부터는 정리해본 내가 프로젝트에서 어떤 인증방식을 적용할지에 대한 내용이다.

소셜 로그인은 웹표준으로 AOuth 프로토콜을 사용하고 있으니, 만약 사용하고 싶다면 AOuth 프로토콜에 맞춰 적용하면 된다.
그러나 나의 서비스에서 자체적으로 인증을 처리하는 경우 세션을 사용하는 것이 유리해보인다. 그 이유는 다음과 같다.

  1. 보안
  2. scale up과 같은 확정성에 대한 세션방식의 문제는 어느정도 보완과 극복이 가능하지만 stateless한 설계를 고집함으로서 오는 보안 문제(토큰 탈취, 로그아웃 구현 불가)는 해결책이 없다.

JWT authentication is inherently less secure than a plain old session, and should only be used if there is a need. Many times when token auth is used, it is not actually necessary, just fancy.
Fortunately, very few applications actually need to be truly stateless on the server-side.

https://stackoverflow.com/questions/69800098/whats-the-whole-point-of-a-jwt-refresh-token
해당 스택오버플로 답변을 인용한 것이다.

요약하자면 이렇다, "JWT인증 방식은 보안 문제로 인해 반드시 필요한 곳에 사용해야 합니다. 그러나 다행히도 완벽히 서버측의 비상태성을 요구하는 어플리케이션은 거의 없습니다."

참고자료

https://velog.io/@park2348190/JWT%EC%97%90%EC%84%9C-Refresh-Token%EC%9D%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80
https://stackoverflow.com/questions/27726066/jwt-refresh-token-flow
https://stackoverflow.com/questions/69800098/whats-the-whole-point-of-a-jwt-refresh-token

profile
CS/Software Engineer

1개의 댓글

comment-user-thumbnail
2024년 5월 8일

너무 정리를 잘 하시네요 잘 보고 갑니다 ㅎ

답글 달기