[Security] JWT

impala·2023년 7월 19일
0
post-thumbnail

인증에 사용되는 토큰의 일종인 JWT(JSON Web Token)를 알아보기 전에 토큰 외의 다른 인증방식에 대해 먼저 알아보자.

인증 방식의 종류

서버에서 클라이언트를 인증하는 방법은 크게 쿠키, 세션, 토큰방식으로 나눌 수 있다.

쿠키

쿠키란 Key-Value형식의 문자열로, 서버의 정보를 클라이언트의 브라우저에 저장하기 위해 사용한다. 서버에서 응답에 Set-Cookie헤더를 통해 여러 정보들을 클라이언트의 브라우저에 쿠키로 등록하면 이후 매 요청마다 쿠키값이 같이 전송되기 때문에 브라우저를 식별할 수 있다.

쿠키를 이용한 인증방식은 다음과 같은 과정을 통해 이루어진다.

  1. 클라이언트가 서버에 request를 보낸다
  2. 서버에서 response에 Set-Cookie헤더를 추가하여 정보를 저장한다.
  3. 이후 클라이언트의 매 요청마다 자동으로 쿠키값이 Cookie헤더에 실려 전송되고, 서버는 이 값을 바탕으로 클라이언트를 인증한다.

쿠키를 이용한 인증방식은 인증정보를 클라이언트에 저장하기 때문에 서버의 리소스를 절약할 수 있다는 장점이 있지만, 인증정보가 클라이언트에 저장되기 때문에 보안에 취약하고 유출 및 위/변조 위험이 있다. 또한 쿠키의 용량이 제한적이기 때문에(도메인당 20개, 쿠키당 4KB) 많은 정보를 저장할 수 없고, 브라우저끼리 쿠키 공유가 불가능하며 쿠키가 쌓일수록 네트워크에 부하가 심해진다는 단점이 있다.

이러한 단점 때문에 클라이언트의 인증정보를 서버에서 관리하는 세션방식의 인증이 등장하였다.

세션

세션이란 서버에서 보관하는 인증정보로, Key(session_id)-Value(map)형식으로 데이터를 저장한다. 세션방식의 인증에서는 클라이언트와 아무 의미가 없는 session_id를 주고받기 때문에 이 값이 노출되어도 어떤 정보를 보관하고 있는지 알 수 없다는 장점이 있다.

세션을 이용한 인증방식은 다음과 같은 과정을 통해 이루어진다.

  1. 클라이언트가 서버에 request를 보낸다.
  2. 서버에서는 클라이언트의 정보를 세션을 만들어 세션 저장소(메모리 혹은 DB)에 저장하고, 응답헤더의 Set-Cookie에 session_id값을 추가하여 전송한다.
  3. 클라이언트의 쿠키에 세션 아이디가 등록되고, 이후 모든 요청에 세션 아이디가 Cookie헤더에 실려 전송된다.
  4. 서버는 Cookie헤더의 session_id를 바탕으로 세션 저장소에서 세션을 조회하여 클라이언트를 인증한다.

세션을 이용한 인증방식은 클라이언트에 세션 아이디값만 저장하기 때문에, 이 값이 노출되어도 사용자의 정보가 유출되지는 않는다는 장점이 있다. 하지만 세션 아이디 자체가 탈취당할 경우, 공격자는 서버에 클라이언트인 척 위장하여 요청을 보낼 수 있고, 서버에서는 매 요청마다 저장소에서 세션을 찾아야하기 때문에 요청이 많아지면 서버에 부하가 늘어난다는 단점이 있다.

토큰

세션기반 인증방식의 가장 큰 문제점은 사용자의 인증정보가 서버에 저장되어 요청시마다 세션을 조회하면서 많은 오버헤드가 발생한다는 점이었다. 토큰기반 인증방식은 이 문제를 해결하기 위해 인증정보를 다시 클라이언트에 보관한다. 토큰은 서버에서 해당 클라이언트가 인증되었다는 의미로 발급해주는 유일한 문자열로, 토큰을 받은 클라이언트는 이후 요청마다 헤더에 토큰을 추가하여 서버로 전송한다.

토큰을 이용한 인증방식은 다음과 같은 과정을 통해 이루어진다.

  1. 클라이언트가 서버에 로그인한다.
  2. 서버에서는 클라이언트에게 유니크한 토큰을 발급한다.
  3. 클라이언트는 발급받은 토큰을 저장하고, 이후 요청헤더에 토큰을 추가하여 전송한다.
  4. 서버는 토큰을 검증하고, 유효한 토큰이면 요청에 응답한다.

이때 토큰에는 자체적으로 사용자의 정보가 들어있기 때문에, 저장소를 조회하지 않고도 요청을 보낸 클라이언트를 식별할 수 있다.

세션기반 인증방식과 토큰기반 인증방식의 가장 큰 차이점은 서버에서 클라이언트의 상태를 유지하는지 여부이다. 세션방식은 클라이언트의 정보가 담긴 세션을 별도의 저장소에 보관해두어야 하기 때문에 (Stateful) 서버에 부하가 크고, 확장성이 떨어진다. 하지만 토큰방식은 요청마다 토큰을 검증하여 클라이언트를 식별할 수 있기 때문에 클라이언트의 정보를 서버에서 유지하지 않아도 되고 (Stateless), 따라서 세션방식에 비해 부하가 적다.

하지만 토큰은 쿠키/세션에 비해 데이터의 길이가 길어 요청이 많아질수록 네트워크에 부하가 걸리고, 토큰의 페이로드는 암호화되지 않아 중요한 정보를 담을 수는 없다. 또한 특정 토큰을 무력화할 수 있는 방법이 없어 토큰이 탈취당하면 대처가 어렵다는 단점이 있다.

JWT(JSON Web Token)

JWT란 인증에 필요한 정보를 암화화한 토큰의 일종으로, JSON형식의 데이터를 Base64 URL-Safe Encode방식으로 인코딩한 것이다. 또한 토큰에 개인키를 통한 전자서명이 포함되어있기 때문에 위/변조에 안전하다는 장점이 있다.

구조

JWT는 크게 Header, Payload, Signiture 세 부분으로 이루어져 있다. 각 파트는 .을 통해 구분한다.

헤더

{
    "alg": "HS256",
    "typ": "JWT"
}

JWT의 헤더에는 두 가지 필드가 존재한다. alg는 서명을 암호화한 알고리즘의 종류가 담겨있고, typ은 토큰의 유형(JWT)을 나타낸다.

페이로드

{
    "sub": 1234,                        // registed
    "name": "impala",                   // private
    "role": "admin",                    // private
    "iat": 123412341234,                // registed
    "https://velog.io/@impala": true    // public
    ...
}

JWT의 페이로드 부분에는 Claim이라고 부르는 인증정보의 조각들이 담겨있다. Claim은 페이로드의 key-value쌍으로, 서버에서 클라이언트를 인증하기 위해 사용되는 정보가 담겨있다.

Claim의 종류는 Registed claim, Public claim, Private claim 3가지로 각각 다음과 같은 의미를 가진다.

  • Registed claim : 미리 정의(예약)된 클레임으로, 아래와 같은 종류의 클레임 중 선택하여 사용할 수 있다.
    • sub(subject) : 제목
    • iss(issuer) : 발행자
    • iat(issued at) : 발행 시간
    • exp(expireation time) : 만료 시간
  • Public claim : 사용자가 정의할 수 있는 클레임으로, 공개적인 정보를 전달하기 위해 사용한다.
  • Private claim : 클라이언트와 서버가 정보를 주고받기 위해 사용자가 정의하는 클레임으로, 공개되도 무관하지만 클라이언트를 식별할 수 있는 정보를 담는다.

서명

HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload) + "." +
    256-bit-secret
)

JWT의 서명은 헤더에서 선택한 암호화 알고리즘을 사용하여 헤더와 페이로드를 각각 인코딩한 문자열과 서버의 비밀키값을 .으로 연결하여 암화화한다. 이때 헤더와 페이로드는 단순하게 base64로 인코딩된 값이므로 누구나 확인할 수 있지만, 서명은 서버의 비밀키값을 사용하기 때문에 비밀키가 유출되지 않는 이상 위/변조가 불가능하다.

JWT 인증

JWT를 사용한 인증과정은 다음과 같다.

  1. 클라이언트가 서버에 로그인한다.
  2. 서버는 로그인을 처리하고, 인증된 사용자 정보로 인증을 위한 토큰과(Access Token) 갱신을 위한 토큰(Refresh Token)을 발급하고, 응답에 실어 전송한다. 이때 Refresh Token은 별도의 저장소에 저장한다.
  3. 클라이언트는 서버로부터 발급받은 JWT를 저장하고, 이후 요청시 Authorization 헤더에 Access Token을 담아 전송한다.
  4. 서버에서는 Access Token을 검증하고, 토큰으로부터 정보를 추출하여 클라이언트를 인증한다.
  5. Access Token의 시간이 만료되면 서버에서는 Refresh Token을 사용하여 토큰을 갱신한다.

검증 과정에서 서버는 클라이언트로부터 받은 토큰을 파싱하여 헤더와 페이로드를 추출하고, 해당 데이터를 비밀키를 통해 다시 서명을 만들어서 두 토큰을 대조해본다. 이때 전자서명에는 단방향 해시함수를 사용하기 때문에, 클라이언트로부터 받은 토큰이 위/변조된 것이라면 서명값이 달라 위/변조 사실을 인지할 수 있다.

JWT는 일반적인 토큰과 마찬가지로 제 3자에게 토큰을 탈취당하면 이 사실을 알아차리기 어렵기 때문에, Refresh Token을 사용하여 2중으로 인증을 수행한다. Refresh Token 역시 일반적인 JWT이지만 Access Token보다 수명이 길기 때문에 Access Token이 만료되어도 Refresh Token을 사용해 클라이언트를 인증하고 새로운 Access Token을 발급해 줄 수 있다.

JWT의 장단점

장점

  • 전자서명이 포함되어있기 때문에 위/변조에 안전하다
  • 서버측에 인증정보를 저장할 별도의 저장소가 필요하지 않아 리소스를 아낄 수 있다.
  • 토큰에 인증에 사용되는 정보가 포함되어있다.
  • Stateless하기 때문에 서버를 확장하기 편리하다
  • 다른 서비스와도 토큰을 공유할 수 있다
  • API에 사용이 가능하다.

단점

  • 토큰에 인증정보를 포함하기 때문에 민감한 정보가 포함된 경우 유출의 위험이 있다.
  • 정보가 많아질수록 토큰의 길이가 길어져 네트워크에 부하가 생긴다.
  • 페이로드는 암호화되지 않기 때문에 중요한 정보를 넣지 않아야 한다.
  • 특정 토큰만 무력화할 수 있는 방법이 없어 토큰 탈취시 대처가 어렵다.

참고 자료

0개의 댓글