Security/JWT

JWT(JSON Web Token)

snowkit 2022. 5. 10. 09:02

JWT란?

  • JSON Web Token의 약자
  • 일반적으로 사용자 인증을 위해 사용
  • Base64 URL Safe 방식으로 인코딩되어 있기 때문에 누구나 읽을 수 있으므로, 중요한 정보를 입력하지 않도록 주의
  • 기본적으로 상태가 없지만(Stateless), 인증과 관련된 경우 보안상의 이유로 대부분 상태를 갖도록(Stateful) 구현됨

JWT 구성

  • Header, Payload, Signature 세 파트로 나뉘어지는데 각각 .으로 구분
  • 각 파트는 Base64 URL Safe 방식으로 인코딩 되어있음

Header

  • 헤더에는 타입과 해시 알고리즘 정보 2가지가 저장됨
    • 타입: JWT
    • 알고리즘: HMAC SHA256(HS256) 또는 RSA
      {
          "typ": "JWT",
          "alg": "HS256"
      }

Payload

  • claim
    • JWT에서 claim은 key-value 쌍을 의미
    • 여러 claim을 저장하고 있음
      • 주로 사용자의 상태가 저장됨
  • claim type
    • Registered claims
      • 공통적으로 사용하기에 유용하므로 추천되는 claim 목록
      • iss (issuer), exp (expiration time), sub (subject), aud (audience), nbf (not before), iat (issued at), jti (jwt id)
    • Public claims
      • 자유롭게 이름을 정해서 사용할 수 있음(이름 충돌 방지 필요)
      • 이곳에 정의되어 있는 이름을 사용해서 충돌을 방지하거나 UUID 등을 사용해서 충돌 방지
      • 충돌 방지된 이름을 사용하면 Public claim
    • Private claims
      • 자유롭게 이름을 정해서 사용할 수 있음(이름 충돌 방지 필요 없음)
      • 다른 claim과 이름이 겹치지 않도록 주의해서 사용
      • 충돌 방지된 이름을 사용하지 않으면 Private claim
        {
            "sub": "1234567890",// Registered claim
            "name": "John Doe",// Public claim
            "admin": true// Private claim
        }

Signature

  • Header에 정의된 알고리즘으로 Header, Payload, Secret Key를 조합해서 생성
  • JWT 검증을 위해 사용
    • Secret Key는 유출되지 않도록 검증 주체(서버)만 보관하고 있어야 함

JWT 사용법

1. [클라이언트 → 서버] 사용자 인증(로그인)

클라이언트(웹)

  1. 서버: 응답
    • Access token: Response Body로 응답
    • Refresh token: Cookie로 응답
      • 쿠키 옵션
        • HttpOnly: JavaScript가 Cookie에 접근하는 것을 제한하여 XSS 방지
        • Secure: 보안 채널(일반적으로 HTTPS 프로토콜)인 경우만 요청에 Cookie를 포함하여 XSS 방지
  2. 클라이언트: 안전하게 보관
    • Access token: 메모리(private 변수처럼 탈취 위험이 낮은 곳)에 저장하여 XSS와 CSRF 방지
      • 쿠키에 저장되지 않으므로 CSRF 공격 불가능
      • 탈취 위험이 낮은 private 변수에 저장하여 XSS 방지
      • (클라이언트가 SPA가 아닌 경우) 페이지 이동 시 토큰이 사라지기 때문에, 모든 요청마다 Refresh token을 통해 Access token 재발급 필요
    • Refresh token: Cookie에 저장
      • CSRF 공격을 통해 Cookie에 저장된 Refresh token으로 Access token을 재발급 받더라도, 공격자는 Access token을 알 수 없으므로 악의적 요청 불가능

클라이언트(모바일 앱)

  1. 서버: 응답
    • Access token: Response Body로 응답
    • Refresh token: Response Body로 응답
  2. 클라이언트: 안전하게 보관
    • iOS Keychain 이나 Android EncryptedSharedPreferences 같은 보안 저장소에 보관

2. [클라이언트 → 서버] 사용자 인증이 필요한 요청

  1. 클라이언트: Access token과 함께 요청
    1. Request header의 Authorization 필드에 Bearer 방식으로 Access token 세팅
    2. Request body와 함께 요청
  2. 서버: 검증 후 요청 수행
    1. Access token 검증
    2. 검증 통과 시 요청한 로직 수행

Stateless하게 구성한 JWT

검증

  1. Secret key로 토큰 signature 검증
  2. 토큰 만료 시간 검증

장점

  • JWT 검증만 하면 되므로 트래픽 부담 낮음(DB 조회 X)
    • Stateful(세션 기반 인증)인 경우, 요청마다 세션 정보를 저장하고 있는 메모리 DB에서 클라이언트 정보를 확인해야 함

단점

  • 토큰이 탈취되면 서버에서 토큰을 폐기시켜야 하는데, 세션은 초기화 시키면 되지만 JWT는 이런 기능을 제공하지 않는다

Stateful하게 구성한 JWT(토큰 관리 및 보안 강화)

검증

  1. Secret key로 토큰 signature 검증
  2. 토큰 만료 시간 검증
  3. 블랙리스트에 포함된 토큰인지 조회

토큰 탈취(비정상적인 요청) 탐지

  • 비정상적인 요청의 기준은 웹 서버마다 다르다
    • 예시
      • IP 주소, User-Agent: 이전과 다른 위치나 기기에서 요청이 발생한 경우
      • 동시 로그인: Refresh token 또는 Access token이 여러 다른 기기에서 사용되는 경우
      • 비정상적인 활동 패턴: Refresh Token이나 Access Token이 지나치게 자주 사용되는 경우

토큰 탈취 해결책

  • 서버에서 토큰을 블랙리스트 DB에 추가

장점

  • Stateful하게 DB를 사용하면 토큰 관리 가능

단점

  • JWT의 장점은 무상태성(Stateless)인데, 보안을 위해 Stateful하게 구성하면 결국 무상태성이라는 장점이 없어진다
    • DB 조회 필요

세션/쿠키 방식과 JWT 비교

  세션/쿠키 JWT
클라이언트 세션 ID 저장 Access token, Refresh token 저장
Scale-out 분산 환경에서 redis로 세션 공유하여 관리 분산 환경에서 DB로 토큰 블랙리스트 공유하여 관리(보안)
캐시/DB 조회 O O
CSRF 토큰 O X
HTTP 요청 크기 상대적으로 적다
- 세션 ID
- CSRF 토큰
상대적으로 크다
- JWT(필드 추가될수록 토큰 길어짐)

결론

  • 보안을 위해 JWT를 Stateful로 구성한다면 세션/쿠키 방식과 큰 차이가 없다
  • 일회성 인증 등 상태를 유지할 필요 없는 경우는 JWT가 좋은 선택이 될 수 있다

참고