Development/웹

JWT(JSON Web Token)이란?

heondeam 2023. 11. 4. 16:03

배경 지식

인증 (Authentication)

인증은 사용자의 신원을 확인하는 프로세스를 의미한다. 사용자가 시스템 또는 서비스에 로그인할 때, 사용자의 아이디와 비밀번호, 생체 인식, 토큰 또는 다른 인증 수단을 통해 사용자가 누구인지 확인하는 것을 뜻한다. 즉, 인증은 “누구”를 확인하는 프로세스라고 생각하면 된다.

인가 (Authorization)

인가는 인증된 사용자가 특정 리소스 또는 기능에 액세스 할 수 있는 권한을 결정하는 프로세스를 의미한다. 사용자가 로그인에 성공하여 시스템에 액세스했을 때, 인가는 해당 사용자에 대한 권한을 관리하고 이 권한을 기반으로 사용자가 특정 리소스에 접근할 수 있는지를 결정한다. 즉, 인가는 “무엇”에 액세스 할 수 있는지를 결정하는 프로세스로, 사용자의 권한을 검증하여 특정 리소스에 대한 권한은 부여하거나 거부하는 프로세스이다.

HTTP의 특징

Stateless

HTTP는 무상태성(Stateless)의 특징을 가진다. 이는 각각의 HTTP 요청이 서로 독립적이며 이전 요청과 상관 관계가 없다는 것을 의미한다.

요청이 독립적이라는 것은 서버에서 클라이언트에 대한 정보를 유지하지 않는다는 뜻이며, 이러한 특징을 통해서 서버의 부담을 줄이고(클라이언트에 대한 상태를 저장하지 않아도 된다.), 서버의 확장성을 높인다.(확장성을 높인다는 것은 곧 서버가 더 많은 클라이언트 요청을 처리하거나 부하를 감당할 수 있도록 새로운 서버를 추가할 수 있는 가능성?이라고 생각된다.)

Connectionless

HTTP는 비연결성(Connectionless)이라는 특징 또한 가진다. 말 그대로 연결을 유지하지 않는다고 생각해보면 될 것 같다.

한번 HTTP 요청을 보내면, 한번의 응답을 한다. 이는 곧 요청과 응답을 통해서만 서버와 연결할 수 있다는 것이고 응답이 처리되고 나면 연결이 끊어짐을 뜻한다.

연결이 끊어지지 않는다면 어떻게 될까? 우선 클라이언트가 서버와 연결을 계속 지속하는 방식에서 수 많은 클라이언트들과 하나의 서버가 연결을 유지하는 방식으로 통신한다면 서버는 메모리 그리고 CPU 사용량 등에서 큰 부담을 갖게 될 것이고? 이는 곧 응답시간 지연으로 이어질 것이라 생각한다.

HTTP는 비연결성이라는 특징을 통해 위와 같은 상황에서 서버 자원 소모를 최소화 할 수 있을 것이고 , 이는 곧 더 많은 클라이언트들의 요청을 받아들일 수 있다는 것을 의미한다.

웹 애플리케이션에서의 인증 방식

위에서 설명했듯이 웹 애플리케이션과 웹 서버 간의 데이터 전송에 사용하는 HTTP는 Stateless, Connectionless 라는 특징을 가진다.

이러한 특징들에 기반한 웹 애플리케이션의 대표적인 두가지 인증 방식(session, token)이 존재하며, 이에 대해서 알아보자.

Session 기반 인증

세션 기반의 인증 방식은 클라이언트에서 사용자가 로그인을 요청하고 검증이 완료되었을 때 해당 사용자의 정보(= 세션)를 서버 측에 저장해두는 방식이다. 즉 세션을 서버 측의 메모리나 데이터 베이스 등을 통해 저장해두고 관리하며 인가 프로세스를 처리해준다. 이 때 로그인 중인 사용자가 많아지게 된다면? 역시 서버에 부담을 줄 것이고 이는 불필요한 서버 확장..과 같은 문제들을 야기했다. 이러한 문제점들을 보완하고자 토큰 기반의 인증 시스템이 등장했다.

Token 기반 인증

토큰 기반의 인증 방식은 로그인을 통해 인증받은 사용자들에게 토큰을 발급해주고, 서버에 요청을 할 때마다 헤더에 토큰을 함께 보내도록 해서 유효성 검사를 하는 방식으로 인가 프로세스를 처리하는 방식이다. 즉, 서버가 아닌 클라이언트에서 인증 정보를 보관하는 것이다.

이러한 방식은 모던 웹 애플리케이션에서 많이 사용이 되는데, HTTP 와의 동일한 특징인 Stateless 하다는 특징 떄문이 아닐까 하고 생각했다.

 

 

JWT란?

토큰 기반의 인증 방식에서 가장 많이 사용되는 토큰의 종류가 JWT(Json Web Token)이다. 이는 JSON 포맷을 이용해서 self-contained 방식으로 사용자에 대한 정보를 저장하는 Claim 기반의 토큰이다.

즉, JWT를 사용하면, 서버 측에 인증받은 사용자에 대한 정보를 저장할 필요가 없다. (JWT가 자체적으로 모든 정보를 포함하고 있기 때문에..) 이를 곧 “self-contained 방식” 이라고 한다.

구성 요소

JWT는 . 을 구분자로 3가지의 파트의 문자열로 구성되어 있다. Header.Payload.Signature 형식으로 구성된다. 각각의 파트를 자세하게 살펴보자.

jwt 구성 요소

Header

  • 토큰의 타입 및 해싱 알고리즘에 대한 메타데이터가 포함된다. (JWT는 구성 부분이 각각 Base64로 인코딩되어 있으며, 헤더의 내용을 디코딩하면 토큰의 유형과 사용하는 해싱 알고리즘을 알 수 있다.)
  • JSON 형태의 객체가 base64로 인코딩 되는 과정에서 공백이 사라진다.
// header

{
	"alg": "HS256",  // 해싱 알고리즘을 지정한다. 대체로 HMAC SHA256 또는 RSA
	"typ": "JWT"     // 토큰의 타입을 지정한다. 생랼 가능한 필드
}

// encode to base64
const encodedPayload = new Buffer(JSON.stringify(payload)).toString().replace("=", "");

console.log("payload: ", encodedPayload);

/*
	result = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
*/ 

Payload

  • 실제로 전송하려는 정보가 포함되어 있다.
    • 정보의 한 단위를 클레임(Claim)이라고 표현하며, 이는 name: value의 한쌍으로 이루어져 있다.
    • 클레임(Claims)의 종류
      • Registered Claims
        • 토큰에 대한 정보를 담기 위해 이미 등록된 클레임들로 모두 선택적으로 사용이 가능하다.
        • 종류 (JWT 사양에 정의된 7가지 클레임)
          • iss - 토큰의 발급자 (issuer)
          • sub - 토큰의 제목 (subject)
          • aud - 토큰 대상자 (audience)
          • exp - 토큰의 만료시간 (expiration), 시간 형식으로 되어 있어야 하며, 항상 현재 시간 이후로 설정되어야 한다.
          • nbf - Not Before를 의미한다. 토큰의 활성 날짜와 비슷한 개념이다. 즉, 토큰이 유효하기 시작한 날짜를 뜻한다.
          • iat - 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age가 얼마나 되었는지 판단할 수 있다.
          • jti - JWT의 고유 식별자. 주로 중복적인 처리를 방지하기 위해 사용된다.
      • Public Claims
        • JWT를 사용하는 사람들에 의해 정의되는 클레임
        • 공개 클레임들을 충돌이 방지되는 이름을 가져야 하므로, 이름을 URI 형식으로 짓는다.
        {
        	"<https://velopert.com/jwt_claims/is_admin>": true
        }
        
      • Private Claims
        • 서버와 클라이언트 사이에서만 협의한 클레임.
        • 공개 클레임과 충돌이 일어나지 않게 사용해여 한다.,

Signature

  • 서버에서 생성한 토큰을 검증하기 위한 서명이다. 이 서명은 헤더와 페이로드의 내용, 그리고 비밀 키를 사용하여 생성된다. (header의 alg에 정의된 알고리즘이 사용됨.) 이 서명을 통해서 토큰이 유효한지 확인할 수 있다.
  • 즉 서명은 헤더와 페이로드의 내용을 인코딩(base64) 한 후 서버의 비밀 키 + 특정 알고리즘(헤더에 alg)를 이용해서 암호화한 부분이다.
  • 서버는 클라이언트 측에서 보내온 JWT의 서명 부분을 통해 유효성을 검증한다. (헤더나 페이로드가 수정되지 않았음을 증명한다.)

토큰의 암호화 방식

비밀키(HMAC)또는 공개키/개인키쌍(RSA)을 이용한다.

검증 절차

  1. 사용자 로그인
    1. 사용자는 아이디와 비밀번호를 입력해서 서버에 로그인을 요청한다.
  2. JWT 발급
    1. 서버는 요청받은 아이디와 비밀번호에 대해 회원임을 검증하고, JWT를 생성한다 이 때, 비밀 키를 사용해서 토큰을 서명하고 클라이언트에 반환한다.
  3. JWT의 저장 및 전송
    1. 클라이언트는 JWT를 안전하게 저장한다. 웹의 경우 브라우저의 쿠키 OR 웹 스토리지에 저장된다.
    2. 인증이 필요한 API 요청의 경우 Authorization" 헤더에 Bearer 스킴을 사용하여 JWT를 전달한다.
  4. 서버에서 JWT 검증
    1. 클라이언트로부터 수신한 JWT를 서버에서 검증한다.
    2. 서명, 토큰의 유효기간, 페이로드의 클레임 정보 등을 확인한다.
  5. 응답 및 리소스 제공
  6. 토큰의 갱신 (optional)
    1. Access Token의 유효기간이 길다면 그 시간동안 토큰 정보를 탈취당할 수 있다는 것을 뜻한다. 하지만 그렇다고 유효기간이 너무 짧다면 사용자가 로그인을 짧은 주기로 반복해야하는 번거로움이 생긴다.
    2. 이를 위해 Refresh Token이 존재한다. 처음 로그인 시에 서버는 Access Token과 Refresh Token을 모두 보낸다. (Refresh Token의 유효 기간은 Access Token보다 길어야 한다.)
    3. Access Token의 유효기간이 다 되었을 때, Refresh Token을 통해 로그인 없이 Access Token을 재발급 받을 수 있다.
      1. 즉, Refesh를 위한 API가 추가로 필요하다. → setTimeout 함수를 활용할 것.