Software Architecture

쿠키(cookie), 세션(session), 토큰(token), JWT(JSON Web Token)

LeeJaeJun 2024. 6. 16. 12:59
728x90
반응형

쿠키

  • 사이트 방문 시 데이터 저장
    • 사용자가 웹사이트를 방문하면, 브라우저는 서버에 요청을 보냄
    • 서버는 요청에 응답하며, 클라이언트가 요청한 데이터와 함께 쿠키를 포함할 수 있음
    • 브라우저는 이 쿠키를 클라이언트 컴퓨터에 저장
  • 쿠키의 역할:
    • 쿠키는 서버가 클라이언트를 기억하기 위해 브라우저에 저장하는 작은 데이터 조각
    • 예를 들어, 로그인 상태, 사용자 선호 설정(예: 언어 설정) 등 저장 가능
  • 쿠키의 동작 방식:
    • 웹사이트에 다시 방문할 때마다, 브라우저는 해당 도메인과 관련된 쿠키를 서버에 함께 전송(도메인에 따라 제한)
    • 서버는 이 쿠키를 사용하여 클라이언트의 상태를 파악하고 적절히 대응
  • 유효 기간:
    • 쿠키는 서버에서 정한 유효 기간이 있으며, 이 기간이 지나면 자동으로 삭제
    • 세션 쿠키는 브라우저가 닫힐 때 삭제
  • 사용자 조작 가능성:
    • 쿠키는 클라이언트 컴퓨터에 저장(브라우저)되기 때문에 사용자가 임의로 수정하거나 삭제 가능
    • 따라서 민감한 정보(아이디, 비밀번호 등)를 쿠키에 저장하는 것은 위험할 수 있으며, 보안에 취약할 수 있음
      • 따라서 민감한 정보들은 클라이언트가 아니라 서버 쪽에서 관리하도록 해야함 (세션)

 

 

세션과 토큰의 필요성

  • 웹사이트를 이용할 때 쓰는 프로토콜은 HTTP는 stateless
  • stateless라는 것은 서버로 가는 모든 요청이 독립적이라는 것
    • 즉, 요청끼리 연결이 없는 것 (서로를 기억하지 않음)
  • 따라서 요청이 끝나면 서버 입장에서는 클라이언트가 누구인지에 대해 기억하지 않기에 요청을 할 때마다 클라이언트가 누구인지 알려주어야 함
  • 예시
    • 클라이언트가 로그인
    • 이후 클라이언트가 다른 행동(예: 페이지 이동, 데이터 요청 등)을 요청
    • 서버는 각 요청을 독립적으로 처리하므로, 클라이언트가 로그인했는지 알 수 없음
    • 따라서, 별도의 설정이 없으면 서버는 클라이언트가 로그인하지 않은 것으로 간주하고 요청을 거부할 수 있음
  • 로그인 같은 작업은 무거운 작업이기 때문에 매 요청마다 하기에는 부담
    • i.e.) 데이터베이스에 저장된 사용자의 계정과 해시값 등을 꺼내와 사용자 암호를 복잡한 알고리즘으로 계산한 값과 확인
  • 매 요청마다 아이디, 비밀번호를 실어서 요청한다면 보안상 위험
  • 이때, 필요한 별도의 설정이 세션과 토큰!

 

세션

  • 요청이 들어올 때:
    • 서버는 클라이언트가 보낸 쿠키를 받아 세션 ID를 확인
    • 세션 데이터베이스에서 해당 세션 ID와 일치하는 정보를 찾아 사용자 상태를 인식. 
  • 로그인 요청:
    • 사용자가 로그인하기 위해 서버에 아이디와 비밀번호를 보냄
    • 서버는 유효한 아이디와 비밀번호를 확인하고, 사용자에 대한 세션을 생성
    • 생성된 세션 ID(사용자를 구분하기 위한 기한이 짧은 인식키)를 쿠키를 통해 브라우저에 보냄
    • 브라우저는 세션 ID를 쿠키에 저장
  •  페이지 이동 시:
    • 사용자가 같은 웹사이트에서 다른 페이지로 이동할 때, 브라우저는 저장된 세션 ID를 쿠키에 담아 서버에 전송
    • 서버는 쿠키에서 세션 ID를 확인하고, 세션 데이터베이스에서 해당 세션 ID를 조회
    • 유효한 세션 ID인 경우, 서버는 사용자를 인식하고 요청을 처리.
  •  서버 저장소:
    • 정보는 모두 서버의 메모리나 데이터베이스에 저장
    • 사용자는 세션 ID만을 보유
  • 세션 관리의 단점 및 해결 방법
    • 서버 메모리 부족:
      • 현재 로그인한 유저들의 모든 세션 ID를 서버 메모리 또는 DB에 저장해야 함
      • 사용자가 동시에 많이 접속하면 메모리가 부족할 수 있음
    • 메모리의 휘발성:
      • 서버 메모리에 문제가 생기면 세션 정보가 사라질 수 있음
      • 서버 재부팅 시 클라이언트는 다시 로그인해야 함
    • 데이터베이스 사용:
      • 데이터베이스에 세션 정보를 저장하면 접속마다 DB에 접근해야 하므로 속도가 느려질 수 있음
      • 서버 여러 대를 운영할 경우 로드 밸런싱 문제로 세션 유지가 어려울 수 있음
        • 1번 서버에서 로그인을 요청해서 세션이 생성되면 세션 ID는 1번 서버의 세션DB에 존재할 거임. 근데 그 다음 request를 3번 서버에다가 하게되면 3번 서버 입장에서는 해당 세션 ID가 자신의 세션DB에 없으니 문제 발생
    •  해결 방법:
      • 사용자 요청을 항상 동일한 서버로 보내거나, 공용 데이터베이스 서버를 사용 -> 느려짐
      • Redis나 MemCached와 같은 메모리형 데이터베이스 서버를 사용하여 세션을 관리
        • 이 방법 또한 재부팅 시 리스크가 있지만, 속도와 확장성 측면에서 유리
  • 쿠키의 역할
    • 쿠키는 세션 ID를 전달하는 매개체 역할
  • 모바일 앱에서의 세션 관리
    • iOS 및 Android 앱에서는 브라우저 쿠키를 사용할 수 없으므로, 세션 관리를 위해 토큰을 사용

 

토큰

  • 토큰은 문자열(string)
  • 클라이언트는 토큰을 서버에 보내고 서버는 세션 DB에서 해당 토큰과 일치하는 유저를 찾음

 

JWT(JSON Web Token)

사용자가 로그인하면 서버는 JWT를 발급하여 사용자를 인증하고, 이후 요청마다 해당 토큰을 사용하여 사용자를 인식하고 권한 부여

(서버는 사용자들의 상태를 따로 저장해둘 필요가 없이, 비밀키만 가지고 토큰을 스캔해서 사용자들을 관리)

  • 사용자가 받아서 가지고 있는 토큰 자체에 정보들이 들어있어서 서버가 요청마다 일일이 데이터베이스 접근할 일이 줄음
  • 로그인 과정:
    • 사용자가 아이디와 비밀번호를 서버에 전송
    • 유효한 아이디와 비밀번호인 경우, 서버는 사용자 ID와 같은 정보를 사용하여 JWT를 생성
      • 서버는 세션 정보를 데이터베이스에 저장하지 않고, 사용자 ID와 비밀 키를 이용해 암호화 알고리즘을 통해 서명된 정보를 생성
    • 서명된 정보는 문자열 형태의 토큰(JWT)으로 변환되어 사용자에게 반환
  • 사용자 인증:
    • 사용자는 서버에 요청을 보낼 때 JWT를 포함 (주로 HTTP 헤더에 포함)
    • 서버는 요청에서 JWT를 추출
    • JWT의 서명을 서버의 비밀 키를 사용하여 검증
      • 헤더와 페이로드를 비밀 키와 함께 사용하여 서명을 생성하고, 이를 토큰의 서명과 비교
      • 다르다면 조작!
    • 서명이 유효하고, 토큰의 유효 기간이 지나지 않은 경우, 서버는 토큰의 정보를 기반으로 사용자를 인식하고 권한을 부여
  • 구조:
    • Header (헤더)
      • JWT의 타입과 서명 알고리즘 정보를 포함
    •  Payload (페이로드)
      • JWT의 내용
      • 사용자 ID, 유효 기간, 권한 정보 등
      • JWT의 헤더와 페이로드 자체는 Base64로 인코딩되어 있어 디코딩이 가능하기 때문에 이 안에 비밀번호 등 민감한 정보를 넣지 않도록 주의
    • Signature (서명)
      • 헤더와 페이로드를 결합하고, 서버의 비밀 키를 사용하여 암호화 알고리즘을 통해 생성된 서명
      • 서명은 토큰의 무결성을 검증하는 데 사용
# header
{
  "alg": "HS256", # 서명을 생성하는데 사용될 알고리즘
  "typ": "JWT" # JWT 토큰임을 명시
}
# payload
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)
# secret은 서버의 비밀키이기 때문에 JWT 토큰만 가지고는 알아낼 수 없음

https://jwt.io

 

JWT.IO

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

jwt.io

  • JWT 보안:
    • 암호화 알고리즘: JWT는 일방향 암호화 알고리즘을 사용하여 서명을 생성
    • 일방향 계산: JWT 생성에 사용되는 암호화 알고리즘은 한쪽 방향으로는 계산이 가능하지만, 반대로 복호화는 불가능
    • 비밀 키: 서명 생성에 사용되는 비밀 키는 서버만 알고 있음
    • 보안 유지: 서명을 검증하기 위해 비밀 키가 필요하며, 이는 서버 외부로 유출되지 않기 때문에 보안이 유지

 

세션과 JWT의 차이

세션(Session)

  • Stateful: 서버가 모든 사용자들의 상태를 기억하고 관리
  • 상태 제어:
    • 사용자의 상태를 언제든 제어 가능
    • i.e.) 한 기기에서만 로그인 가능한 서비스를 구현하여, 다른 기기에서 로그인하면 기존 기기에서 로그아웃 시킬 수 있음, 불법적인 행동을 하는 사용자를 강제 로그아웃 시킬 수 있음(세션 삭제), 계정 공유 숫자를 제한하여 현재 몇 명이 로그인되어 있는지 알 수 있음(ex. 넷플릭스)
  • 관리 부담:
    • 세션 정보를 저장하고 관리하기 위해 데이터베이스(DB)가 필요
    • DB의 유지 및 관리 비용이 발생
  • 구현 방식:
    • 서버는 관련된 모든 정보를 세션 DB에 저장
    • 클라이언트가 페이지를 요청하면, 서버는 세션 ID를 DB에서 찾아 사용자 상태를 확인

JWT(JSON Web Token)

  • Stateless: 서버는 사용자 상태를 기억하지 않으며, 시간에 따라 바뀌는 상태값을 가지지 않음
  • 상태 제어 불가능:
    • 이미 발급한 토큰을 회수 불가
    • 발급된 토큰의 정보를 서버가 기록하거나 추적하지 않음
    • i.e.) 사용자가 불법적인 행동을 해도 토큰을 강제 무효화할 수 없음, 토큰이 만료되기 전까지는 유효
  • 관리 부담 감소:
    • 별도의 DB를 사용할 필요가 없음
  • 구현 방식:
    • 서버는 사용자 인증 정보를 토큰에 저장
    • 클라이언트가 페이지를 요청하면, 서버는 토큰이 유효한지만 검증하고, DB에 접근하지 않음

 

JWT에서 발생가능한 문제

1. alg: none 공격

  • 문제: JWT의 헤더 부분에 alg를 none으로 설정하면 일부 서버가 서명 검증을 하지 않고 토큰을 허용하는 경우가 있음
  • 해결 방안: JWT를 검증할 때 alg 값이 none인 토큰을 거부하도록 설계해야 함.(허용할 알고리즘 목록을 명시적으로 설정하고, none이 포함되지 않도록)

2. Base64 디코딩

  • 문제: JWT는 Base64Url로 인코딩되어 있기 때문에 누구나 쉽게 디코딩 가능
  • 해결 방안: 민감한 정보를 페이로드에 포함하지 않도록 해야 함. 중요한 데이터는 토큰 외부에서 관리하고, 토큰에는 최소한의 정보만 포함시켜야 함

3. 시크릿 키 문제

  • 문제: 짧거나 예측 가능한 시크릿 키(예: secret)를 사용하면 brute force 공격에 취약
  • 해결 방안:
    • 긴 문자열과 복잡한 시크릿 키를 사용하여 예측 가능성을 낮추기
    • 시크릿 키를 공유하지 않고, 안전하게 관리
    • 생성용 키와 검증용 키를 분리하여 사용(private key, public key)

4. JWT 탈취

  • 문제: JWT가 탈취되면 토큰을 회수하거나 사용 정지 시키기 어려움
  • 해결 방안:
    • HttpOnly 쿠키를 사용하여 클라이언트 측에서 JWT를 안전하게 저장
    • JWT Blacklist: 특정 JWT를 차단하기 위한 블랙리스트를 운영할 수 있지만, 이는 세션 방식과 유사하게 DB를 통해 관리해야 하므로 JWT의 장점을 일부 상실하게 됨
    • JWT 유효기간 단축: JWT의 유효기간을 짧게 유지하여 탈취된 토큰이 오래 사용되지 않도록 함. 재발급이 빈번하므로 Refresh Token과 함께 사용
    • Refresh Token: JWT의 유효기간이 만료될 때마다 새로운 토큰을 발급받기 위해 사용. Refresh Token이 탈취될 경우, 이를 통해 새로운 JWT를 계속 발급받을 수 있기에  Refresh Token Rotation을 적용하여 안정성 증가시킬 수 있음
      • Refresh Token Rotation: 매번 새로운 Refresh Token을 발급하여 이전 토큰을 무효화하는 방법. 사용자가 새로운 JWT와 Refresh Token을 발급받을 때마다 기존의 Refresh Token을 무효화하고, 새로운 Refresh Token을 발급
728x90
반응형