HTTP의 비연결성과 무상태성

Untitled

  • HTTP : 클라이언트의 요청 ↔ 서버의 응답
  • 요청에 대한 응답이 처리되면 연결이 끊어짐(Connectionless) → 클라이언트에 대한 이전의 상태정보 및 현재 통신 정보가 남아있지 않음
장점 불필요한 자원 낭비를 줄일 수 있음. ex) 서버가 여러 클라이언트와 연결 유지할 경우 자원 낭비가 심해짐
단점 서버가 클라이언트를(클라이언트에 대한 이전의 상태 정보 및 현재 통신 정보) 식별할 수 없음.

쿠키

쿠키는 <이름>=<값> 형태를 지니는 단순한 문자열입니다. 서버와 브라우저는 기본적으로 HTTP 메시지 안에 이 쿠키를 담아서 주고 받게 됩니다.

클라이언트 요청헤더에 쿠키가 담겨서 서버로 전송되고, 서버는 쿠키의 정보를 참고한다.

쿠키는 세션 쿠키와 지속 쿠키로 나뉩니다

Set-Cookie: <쿠키 이름>=<쿠키 값>; Expires=종료 시점
Set-Cookie: <쿠키 이름>=<쿠키 값>; Max-Age=유효 기간

세션 쿠키

  • 사용자가 사이트를 탐색할 때, 관련한 설정과 선호 사항들을 저장하는 임시 쿠키 브라우저를 닫으면 삭제
  • discard 파라미터, expires, max-age 없으면 세션 쿠키

지속 쿠키

  • 브라우저를 닫거나 컴퓨터를 재시작해도 남아있음. 사용자가 주기적으로 방문하는 사이트에 대한 설정 정보나 로그인 이름을 유지하려고 사용

쿠키의 용도

  • 세션 관리(Session management) - 서버에 저장해야 할 로그인, 장바구니, 게임 스코어 등의 정보 관리
  • 개인화(Personalization) - 사용자 선호, 테마 등의 세팅
  • 트래킹(Tracking) - 사용자 행동을 기록하고 분석하는 용도

단점

  1. 보안에 취약하다.
    1. 요청 시 쿠키의 값을 그대로 보낸다 (id, pw에 대한 민감정보까지 노출)
    2. 유출 및 조작의 위험이 존재
  2. 쿠키에는 용량 제한(4KB)이 있어 많은 정보를 담을 수 없다.
  3. 웹 브라우저마다 쿠키에 대한 지원 형태가 달라서 브라우저간 공유가 불가능하다.
  4. 쿠키의 사이즈가 커질수록 네트워크에 부하가 심해진다.
  5. 서버는 매번 id, pw를 받아 인증해야하는 불편함이 있으며 조작된 데이터가 넘어오는 경우를 방지할 수 없다.

세션

세션(Session)이란 HTTP 프로토콜을 사용하는 인터넷 사용자가 어떤 웹사이트를 방문할 경우, 사용자와 서버 사이의 연결을 확인하기 위한 정보입니다.

Untitled

  1. 사용자가 서버에 연결 요청을 보냅니다.
  2. 서버는 이때 세션(Session)ID가 생성 및 저장됩니다
  3. 그리고 이러한 세션 정보를 쿠키에 입력하여, 함께 연결 응답 정보를 전송 합니다.
  4. 사용자는 다음에 연결을 수행할 때 쿠키와 함께 데이터를 요청합니다.
  5. 서버는 이때 쿠키를 확인하고, 쿠키에 입력된 세션정보를 통해 사용자가 누구인지 확인한 후 응답을 하게 됩니다.
구분 쿠키  Cookie 세션 Session
저장위치 클라이언트 서버
라이프사이클(만료시점) 쿠키 저장시 설정 브라우저 종료 시 삭제
보안 비교적 취약 안전
속도 빠름 비교적 느림

JWT

JWT 는 . 을 구분자로 3가지의 문자열로 되어있습니다. 구조는 다음과 같이 이루어져있습니다

Untitled

  • typ : 토큰의 타입
  • alg : 해싱 알고리즘 (signature)
{
  "typ": "JWT",
  "alg": "HS256"
}

Payload

  • claim - payload의 한 조각, 단위
    • 등록된 클레임 - 등록된 클레임들은 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들입니다. 등록된 클레임의 사용은 모두 선택적 (optional)이며, 이에 포함된 클레임 이름들은 다음과 같습니다:
      • iss: 토큰 발급자 (issuer)
      • sub: 토큰 제목 (subject)
      • aud: 토큰 대상자 (audience)
      • exp: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어있어야합니다.
      • nbf: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념입니다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않습니다.
      • iat: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있습니다.
      • jti: JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용합니다.
    • 공개 클레임 - 공개 클레임들은 충돌이 방지된 (collision-resistant) 이름을 가지고 있어야 합니다. 충돌을 방지하기 위해서는, 클레임 이름을 URI 형식으로 짓습니다.
      {
          "https://velopert.com/jwt_claims/is_admin": true
      }
    
    • 비공개 클레임 - 등록된 클레임도아니고, 공개된 클레임들도 아닙니다. 양 측간에 (보통 클라이언트 <->서버) 협의하에 사용되는 클레임 이름들입니다. 공개 클레임과는 달리 이름이 중복되어 충돌이 될 수 있으니 사용할때에 유의해야합니다.
      {
          "username": "velopert"
      }
    

Signature

  • 토큰 검증 역할. 서버에서 토큰을 검증하여 payload과 header가 변조되지 않았는지 여부 확인
  • 서명(Signature)은 토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드이다. 서명(Signature)은 위에서 만든 헤더(Header)와 페이로드(Payload)의 값을 각각 BASE64로 인코딩하고, 인코딩한 값을 비밀 키를 이용해 헤더(Header)에서 정의한 알고리즘으로 해싱을 하고, 이 값을 다시 BASE64로 인코딩하여 생성한다.

Untitled

JWT 토큰 방식 순서

  1. 사용자가 회원가입을 하면서 서버의 DB에 사용자의 정보가 저장된다.
  2. 사용자가 로그인하면 클라이언트가 로그인한 정보(아이디, 비밀번호 등)를 서버로 보낸다.
  3. 서버에서는 받은 정보를 DB에 확인해서 인증이 되면 Access token(JWT 토큰)을 생성하여 발급한다.
  4. 클라이언트는 발급받은 토큰을 브라우저 내 저장소(Cookie, Web Storage)에 저장한다.
  5. 이후 서버에 요청을 보낼 때마다 서버에게서 받았던 토큰을 헤더에 담아서 함께 보낸다.
  6. 서버에서는 토큰의 유효기간과 토큰의 ‘서명’부분을 확인하여 사용자 정보를 확인(인가)한 뒤, 요청받은 정보를 클라이언트에게 보낸다.

이렇게 하면 모든 정보는 클라이언트 측의 토큰 내에 저장되기 때문에

서버에서는 사용자의 정보를 따로 관리할 필요가 없어진다.

=> 서버의 무상태성 구현 (서버에서 ‘상태’를 관리하지 않음)

refresh token은 처음에 로그인을 완료했을 때 access token보다 긴 유효기간을 가지고(예: 90일) access token과 동시에 발급된다.

access token이 만료되었을 때 refresh token이 유효하다면 유저가 새로 로그인을 하지 않아도 서버에서 새 access token을 발급한다.

Refresh 토큰은 기존 Access 토큰이 만료된 후 추가 Access 토큰을 발급받기 위한 기준으로 사용되며, access 토큰과 마찬가지로 유효기간이 지나면 만료되지만 만료되기까지의 기간은 훨씬 길다.

  1. refresh token 덕분에 access token의 유효기간을 짧게 해둘 수 있어 더 안전한 통신이 가능하다.
  2. refresh token마저 만료되면 사용자는 새로 로그인을 해야 하지만, 그 전까지는 로그인을 안 해도 된다.

하지만 refresh token도 해킹될 가능성은 있기 때문에 적절한 유효기간 설정이 필요하다.

Refresh Token을 어디에 저장하는 것이 효율적이고 안전한지에 대해 가장 좋은 방법은 DB에 저장하는 것입니다.

Refresh Token 값을 DB에 저장하고, 클라이언트는 인덱스 값을 쿠키나 로컬 스토리지에 저장합니다. 쿠키에 저장하는 경우, 만료 기간을 길게 잡으면 충분히 끊임없이 로그인된 상태를 유지할 수 있습니다. 이렇게 하면 Refresh Token의 값을 노출시키지 않고 인덱스 값만 클라이언트에 노출되므로 보안상 더 안전하게 저장할 수 있습니다. 더 나아가 인덱스 값도 사용자의 아이디나 추가 값을 조합하여 해시로 생성하여 사용하면 보안 측면에서 더 유리합니다.

그러나 만약 Refresh Token이 탈취된다면 대비하는 방법으로 RTR(Refresh Token Rotation)이 존재합니다.

RTR은 Refresh Token을 한 번만 사용할 수 있도록 만드는 것입니다. Refresh Token을 사용하여 새로운 Access Token을 발급받을 때마다 Refresh Token도 새롭게 발급받는 것이죠. 이렇게 하면 이미 사용된 Refresh Token을 검사하여 서비스 측에서 탈취를 확인할 수도 있습니다.

따라서, Refresh Token은 보안적인 측면을 고려하여 DB에 저장하고, RTR과 같은 방법을 적용하여 안전하게 관리하는 것이 좋습니다.

질문 리스트

JWT에 대해서 설명해주세요.

✅ JWT는 JSON Web Token의 약자로, 웹 개발 및 API 인증에서 인증 및 정보 교환에 사용되는 토큰 기반의 인증 방식 중 하나입니다.

JWT를 사용하는 이유가 뭐죠?

✅ 가장 큰 이유로는 HTTP의 무상태성이라는 특징과 잘 맞기 때문입니다. JWT는 상태를 서버에 저장하지 않고, 클라이언트 측에 토큰을 저장하여 상태를 유지합니다. 이로 인해 서버가 확장성을 향상시키고, 상태를 관리하는 복잡성이 줄어들게 됩니다.

세션 방식하고는 무슨 차이가 있죠?

✅ 상태 정보 저장 방식이 가장 큰 차이점이라고 볼 수 있습니다.

✅ 세션은 서버 측에 상태 정보를 유지합니다. 유저가 여러 명일 경우, 그만큼 메모리에 부하가 걸릴 수 있습니다.

✅ 클라이언트에 저장되어 확장성이 뛰어난 JWT와 달리, 각 서버의 메모리나 데이터베이스에 저장되기 때문에 확장성에 영향을 줄 수 있습니다.

그렇다면 세션보다 JWT가 훨씬 좋은건가요?

✅ 그렇지도 않습니다. 토큰을 클라이언트가 가지고 있어서 탈취를 당하게 되었을 경우 access token을 무효화 시킬 수 있는 방법이 따로 없습니다. 그렇기 때문에 토큰이 노출될 경우, 보안에 취약해 질 수 있습니다.

서버의 수평적 확장이 일어나면 무조건 JWT를 써야하는 건가요?

✅ 아닙니다. 세션 방식을 사용하는 경우 여러 서버간에 세션 데이터를 공유하고 동기화하는 기술인 세션 클러스터링을 사용한다면 세션 방식으로도 다중 서버에서의 인증 및 정보 교환이 가능합니다.


참조

[쿠키 1부: HTTP로 설명하는 쿠키(cookie) Engineering Blog by Dale Seo](https://www.daleseo.com/http-cookies/)

HTTP 완벽 가이드 - 예스24 (yes24.com)

쿠키 / 세션 / JWT / 보안 이슈 (tistory.com)

RAON CTF - WEB Essential

톰캣 에서는 어떻게 JSESSIONID 를 만드는 것일까? (tistory.com)

쿠키와 세션 개념 (tistory.com)

[[JWT] JSON Web Token 소개 및 구조 VELOPERT.LOG](https://velopert.com/2389)

JWT 토큰과 무상태성(Stateless) (velog.io)

토큰(token)은 어떻게 탈취 당하는가? (velog.io)

CSRF(Cross Site Request Forgery) 공격, 사례, 방어 방법 — 이로운 개발하기 (tistory.com)

JWT의 Refresh Token과 Access Token은 어디에 저장해야 할까? (tistory.com)

JWT(Json Web Token)란 무엇인가!! 정리해보쟈~ (velog.io)

[Redis] RefreshToken은 왜 Redis를 사용해 관리할까? — Back world (tistory.com)

Categories:

Updated:

Leave a comment