본문 바로가기

알쓸코잡

JWT(JSON Web Token) 으로 3행시 해보겠습니다

JWT(JSON Web Token)으로 3행시 해보겠습니다

J : 제이슨 웹 토큰이

W : 왜 중요한지 모른다면

T : 토킹 어바웃


JWT(Json Web Token) 이란

인증에 필요한 정보들을 암호화시킨 JSON 토큰

JWT는 세 부분으로 구성되고 .(점)을 구분자로 나누어지는 세 가지 문자열의 조합


세 가지 문자열?

  • Header
  • Payload
  • Signature


Header

Header는 일반적으로 두 가지 정보를 포함

Header는 Base64URL로 인코딩 되어 생성

  • alg : 사용되는 암호화 알고리즘(HMAC SHA256, RSA)
  • typ : 토큰의 타입을 나타냄. 일반적으로 "JWT"
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload(Claims)

실제 전달하고자 하는 정보를 포함

값-쌍 형식으로 이루어진 한 쌍의 정보를 클레임(Claim)이라고 부름

클레임은 일반적으로 세 가지 유형으로 구분

Header와 동일하게 Base64URL로 인코딩 되어 생성

Registered claims 
  • 표준 클레임 이름들로, 예약되어 있는 의미를 가짐
  • 예를들어 'iss'는 발행자, 'exp'는 만료시간, 'sub'는 주제, 'aud'는 대상
Public claims
  • 충돌을 방지하기 위해 URL 형식으로 이름이 정의됨
  • 공개용 정보 전달을 위해 사용
Private Claims
  • 클라이언트와 서버 간에 협의된 이름-값 쌍
  • 해당하는 당사자들 간에 정보를 공유하기 위해 만들어진 사용자 지정 클레임
  • 외부에 노출되어도 상과없지만, 유저를 특정할 수 있는 정보를 포함

 

예시

{
  "sub": "1234567890",                     // Registered claim: 'sub'는 주제(Subject)를 나타냄, 보통 고유 식별자가 들어감
  "exp": 1615556800,                       // Registered claim: 'exp'는 만료시간(Expiration Time)을 나타냄, Epoch 시간으로 표현됨
  "https://example.com/user_role": "admin", // Public claim: 충돌을 피하기 위해 URI 형태로 이름이 지정됨
  "name": "Hgom",                      // Private claim: 'name'은 특정 어플리케이션에서 정의한 것
  "admin": true                            // Private claim: 'admin' 역시 특정 어플리케이션에서 정의한 것
}

Signature

Header와 Payload를 암호화한 값

 

생성 과정
  1. Header와 Payload를 각각 Base64Url 인코딩
  2. 인코딩 된 Header와 Payload를 연결하고, 이 문자열에 대한 서명을 생성
  3. 이때 "secret"이라고 하는 서버가 가진 비밀키를 사용
  4. 암호알고리즘은 Header 정보의 alg 알고리즘을 사용함
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

 

예시

import hashlib
import hmac
import base64
import json

def base64_url_encode(data):
    """데이터를 Base64로 인코딩하고 URL-safe하게 만듭니다."""
    return base64.urlsafe_b64encode(data).rstrip(b'=')

# 헤더 정보
header = {
    "alg": "HS256",
    "typ": "JWT"
}

# 페이로드 정보
payload = {
    "sub": "1234567890",
    "name": "Hgom",
    "admin": True
}

# 비밀 키
secret = "secret"

# 헤더와 페이로드를 Base64Url 인코딩
encoded_header = base64_url_encode(json.dumps(header).encode())
encoded_payload = base64_url_encode(json.dumps(payload).encode())

# 인코딩된 헤더와 페이로드를 결합
signing_input = encoded_header + b'.' + encoded_payload

# HMAC SHA256 서명 생성
signature = hmac.new(
    secret.encode(),
    msg=signing_input,
    digestmod=hashlib.sha256
).digest()

# 서명을 Base64Url 인코딩
encoded_signature = base64_url_encode(signature)

# 모든 부분을 결합하여 최종 JWT 토큰 생성
jwt_token = signing_input + b'.' + encoded_signature

print("생성된 JWT 토큰:", jwt_token.decode())

JWT 토큰을 사용한 인증 프로세스

1. 사용자 로그인

  • 사용자가 자신의 아이디와 비밀번호 등의 인증 정보를 입력하여 서버에 전송

2. 서버에서 인증

  • 받은 정보를 확인해서 사용자의 인증 여부 결정
  • 인증 성공한 경우, Header, PyaLoad, Signature를 바탕으로 JWT 토큰을 생성함

3. 토큰 발행

  • 생성된 JWT 토큰을 사용자에게 반환
  • 일반적으로 HTTP 응답 헤더의 "Authorization' 필드에 담겨 전송되거나, 쿠키에 저장할 수도 있음
from flask import Flask, jsonify, request, make_response
import jwt
import datetime

app = Flask(__name__)

# 임시로 사용할 비밀 키
app.config['SECRET_KEY'] = 'mysecretkey'

@app.route('/login', methods=['POST'])
def login():
    auth_data = request.get_json()
    
    username = auth_data.get('username')
    password = auth_data.get('password')

    if username == 'user' and password == 'password':
        # JWT 토큰 생성
        token = jwt.encode({'user': username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)}, app.config['SECRET_KEY'], algorithm='HS256')

        # 토큰을 Authorization 헤더에 담아 반환
        return make_response(jsonify({'message': 'Token is in the Authorization header'}), 200, {'Authorization': f'Bearer {token.decode("utf-8")}'})

 

4. 클라이언트 저장

  • 클라이언트(웹 브라우저, 모바일 앱 등)는 받은 토큰을 저장함

5. 사용자 인증

  • 사용자가 인증이 필요한 서비스에 접근하려 할 때마다, 저장된 토큰을 HTTP 요청 헤더에 담아 서버에 전송

6. 서버에서 토큰 검증

  • 서버는 요청 받을 때마다 헤더의 토큰을 검증함. 토큰의 Signature와 만료 시간 등을 체크하여 해당 사용자가 유효한지 확인

7. 요청 처리

토큰이 유효하면, 서버는 요청을 처리함