JWT (Json Web Token) Authentication

아래는 “JWT(JSON Web Token)” 주제에 대한 체계적이고 깊이 있는 IT 백엔드 개발자 관점의 조사와 분석 결과입니다.


1. 태그 (Keyword-Tag)


2. 카테고리 계층 구조 검토

제시된 계층 구조:
Computer Science and Engineering > Cybersecurity and Information Security > Access Control > Implementations > Token-Based Authentication

분석 및 근거:
JWT 는 인증과 권한 부여 (Access Control) 의 한 방식으로, Token-Based Authentication 의 대표적인 구현체입니다. 따라서,


3. 주제 요약 (200 자 내외)

JWT 는 사용자 인증 정보를 JSON 형식으로 담고, 암호화 서명으로 위변조를 방지하는 토큰 기반 인증 표준으로, 서버 세션 없이도 안전하게 사용자 인증과 정보 교환이 가능하다 43.


4. 전체 개요 (250 자 내외)

JWT 는 Header, Payload, Signature 로 구성된 토큰으로, 인증 및 권한 정보를 안전하게 전달할 수 있도록 설계되었다. 서버는 세션 상태를 유지하지 않고도 토큰 검증만으로 사용자를 인증할 수 있어, 분산 시스템과 마이크로서비스 환경에 적합하다 45.


5. 핵심 개념


6. 주제와 관련하여 조사할 내용

6.1. 배경

6.2. 목적 및 필요성

6.3. 주요 기능 및 역할

6.4. 특징

6.5. 핵심 원칙

6.6. 주요 원리

6.7. 작동 원리 (다이어그램/워크플로우)

sequenceDiagram
    participant Client
    participant AuthServer
    participant ResourceServer

    Client->>AuthServer: 로그인(아이디/비밀번호)
    AuthServer->>Client: JWT 발급
    Client->>ResourceServer: JWT 포함 요청
    ResourceServer->>ResourceServer: JWT 검증
    ResourceServer->>Client: 리소스 반환

6.8. 구조 및 아키텍처

구성 요소

구조 다이어그램

1
2
3
4
5
6
7
8
9
+-------------------+     +-------------------+     +-------------------+
|     Header        |     |     Payload       |     |    Signature      |
|-------------------|     |-------------------|     |-------------------|
| typ: JWT          |     | iss: issuer       |     | HMAC-SHA256(      |
| alg: HS256        |     | sub: subject      |     |   base64(header)  |
+-------------------+     | exp: expiration   |     |   + "." +         |
                          | iat: issued at    |     |   base64(payload),|
                          | ...custom claims  |     |   secret)         |
                          +-------------------+     +-------------------+

6.9. 구현 기법

6.10. 장점

구분항목설명
장점Stateless서버에 세션 저장 불필요, 확장성 향상
Self-contained모든 인증 정보가 토큰 내에 포함, DB 조회 불필요
Cross-domain도메인 간 인증 가능, SPA/모바일 앱에 적합
CompactHTTP 헤더 등에 쉽게 포함 가능, 네트워크 부하 감소
신뢰성서명으로 위변조 방지, 신뢰할 수 있는 인증 정보 제공

6.11. 단점과 문제점 그리고 해결방안

단점

구분항목설명해결책
단점Stateless 한계토큰 만료 전 강제 폐기 (revoke) 어려움짧은 만료 시간, 블랙리스트
Payload 노출Base64 인코딩만으로 암호화되지 않아 민감정보 노출 위험민감정보 포함 금지
토큰 크기클레임이 많아지면 토큰 크기 증가, 네트워크 부하필요한 클레임만 포함

문제점

구분항목원인영향탐지 및 진단예방 방법해결 방법 및 기법
문제점토큰 탈취XSS, CSRF 등 보안 취약점인증 우회로그 분석, 모니터링보안 헤더 적용짧은 만료 시간, HTTPS
알고리즘 변경 공격alg:none 등 취약 알고리즘서명 우회라이브러리 검증알고리즘 강제 지정라이브러리 업데이트
재생 공격토큰 재사용인증 우회jti 클레임 사용일회용 토큰jti 클레임 활용

6.12. 도전 과제

6.13. 분류 기준에 따른 종류 및 유형

분류 기준종류/유형설명
서명 방식HMAC, RSA, ECDSA대칭키/비대칭키 기반 서명
암호화 유무JWS, JWE서명만 (JWS), 암호화 포함 (JWE)
클레임 타입Registered, Public, Private표준, 공개, 사설 클레임

6.14. 실무 사용 예시

시스템/목적사용 목적효과
웹/모바일 앱 인증사용자 인증/권한 부여세션 관리 불필요, 확장성
API Gateway마이크로서비스 인증중앙 집중식 인증 관리
SSO(Single Sign-On)다중 시스템 통합 인증사용자 편의성, 보안 강화

6.15. 활용 사례

사례: 마이크로서비스 환경에서의 JWT 활용

6.16. 구현 예시 (Python)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import jwt
import datetime

# 비밀키
secret = 'my_super_secret'
# Payload
payload = {
    'sub': 'user123',
    'name': 'John Doe',
    'iat': datetime.datetime.utcnow(),
    'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
# 토큰 생성
token = jwt.encode(payload, secret, algorithm='HS256')
print(token)

# 토큰 검증
decoded = jwt.decode(token, secret, algorithms=['HS256'])
print(decoded)

6.17. 실무에서 효과적으로 적용하기 위한 고려사항 및 주의할 점

항목설명권장사항
토큰 만료 시간너무 길면 보안 위험, 너무 짧으면 사용자 불편1 시간 내외 권장
민감정보 포함 금지Payload 는 Base64 인코딩만으로 암호화되지 않음민감정보 포함 금지
서명 알고리즘 강제alg:none 등 취약 알고리즘 방지알고리즘 강제 지정
HTTPS 사용토큰 탈취 방지반드시 HTTPS 사용
라이브러리 업데이트보안 취약점 대응최신 버전 유지

6.18. 최적화하기 위한 고려사항 및 주의할 점

항목설명권장사항
클레임 최소화불필요한 클레임 포함 금지필요한 클레임만 포함
토큰 크기 관리클레임이 많아지면 네트워크 부하클레임 최소화
검증 부하 분산대규모 시스템에서 토큰 검증 부하 분산API Gateway 에서 검증
짧은 만료 시간보안 강화1 시간 내외 권장

7. 기타 사항


8. 주제와 관련하여 주목할 내용

카테고리주제항목설명
보안JWT서명/암호화HMAC, RSA, ECDSA 등 다양한 알고리즘
아키텍처JWTStateless서버 세션 불필요, 확장성
실무 적용JWT마이크로서비스중앙 집중식 인증, 서버 간 신뢰 구축
표준JWTRFC 7519공식 표준 문서

9. 반드시 학습해야할 내용

카테고리주제항목설명
보안JWT서명/암호화HMAC, RSA, ECDSA 등 알고리즘 이해
표준JWTRFC 7519공식 표준 문서 학습
실무JWT라이브러리 활용PyJWT, jose 등 라이브러리 사용법
아키텍처JWTStateless서버 세션 불필요, 확장성 이해

용어 정리

카테고리용어설명
표준RFC 7519JWT 공식 표준 문서
보안HMAC해시 기반 메시지 인증 코드
보안RSA비대칭키 암호화 알고리즘
보안ECDSA타원곡선 디지털 서명 알고리즘
아키텍처Stateless서버에 상태 저장 불필요
실무PyJWTPython JWT 라이브러리

참고 및 출처


🏷️ 1. Topic Tags

Token-Based-Authentication
Access-Control
JSON-Web-Token
Stateless-Sessions


2. Classification Hierarchy Validity

The categorization—Computer Science and Engineering > Cybersecurity and Information Security > Access Control > Implementations > Token-Based Authentication—is appropriate. JWT sits precisely under “Token-Based Authentication” as a stateless token mechanism for access control. It aligns with industry classification of auth methods, no adjustments needed.


3. Concise Description (~200 자)

JWT(JSON Web Token) 는 헤더 (header), 페이로드 (payload), 서명 (signature) 로 구성된 자체 포함형 (stateless) 토큰으로, 클라이언트 - 서버 간 인증 및 권한 정보를 안전하게 전달하고 검증하는 데 사용되며, 세션 저장 없이 유효 시간 (exp), 발급자 (iss), 수신자 (aud) 등의 클레임을 포함합니다.


4. Overview (~250 자)

JSON Web Token(JWT) 는 RFC 7519 에 따라 정의된 URL-safe 한 JSON 기반 토큰 형식으로, HMAC 또는 RSA/ECDSA 로 서명된 자체 포함형 JWS 혹은 암호화된 JWE 형태로 제공됩니다. 헤더에는 타입과 알고리즘, 페이로드에는 표준 및 사용자 정의 클레임, 서명으로 무결성을 보장하며, OAuth2 및 SSO, 마이크로서비스 간 인증에 널리 이용됩니다. 세션 저장이 필요 없는 stateless 방식으로, 확장성과 분산 시스템에 유리하나 만료, 탈취 그리고 revocation 등의 보안 이슈 발생 가능성을 가지고 있어 적절한 설계와 키 관리가 필수적입니다.


5. 핵심 개념

실무 연관성
구현 시 라이브러리 (jwt.io, Auth0, PyJWT 등) 사용, alg=none 취약 방지, 충분한 키 길이, 리프레시 토큰 설계, HTTPS 전송이 필수적입니다.


6. 주요 조사 내용 정리

주요 항목은 5 번에서 포괄했으며, 추가로 다음 항목을 다룹니다.


7. 추가 조사 내용


8. 주목할 내용 요약

카테고리주제항목설명
구조Header-Payload-SignatureJWT 세 부분URL-safe, Base64Url 인코딩된 형식
보안Validation vs Verification처리 차이유효성 체크 vs 서명 및 발급자/수신자 검사
형태JWS vs JWESigned vs Encrypted무결성만 vs 기밀성 + 무결성

9. 반드시 학습해야할 내용

카테고리주제항목설명
키 관리Symmetric vs AsymmetricHS256, RS256 등키 형식과 운영 방식 이해
표준RFC 7519, 7515, 7516JWS/JWE/JWAJWT 및 JOSE 관련 표준 문서
라이브러리 보안JWT 라이브러리 취약점alg=none 등구현 시 주요 보안 위험 요소

🔚 용어 정리

카테고리용어설명
구조Base64UrlURL-safe Base64 인코딩 방식
클레임exp, iat, iss, aud토큰의 유효 기간, 발급 시각, 발급자, 수신자 정보
표준JWS, JWEJSON Signature vs Encryption
공격alg=none 공격서명 없이 공격자가 토큰 위조 가능

📚 참고 및 출처


추가로 심화된 구조 다이어그램, 구현 예제, 장단점 분석, 실무 활용 케이스 등 단계별 분석이 필요할 경우 말씀해주세요!

아래는 JWT 에 대한 보다 심화된 분석입니다.


🧠 구조 및 아키텍처 (구성 요소 포함)

다이어그램 (Mermaid)

flowchart TD
  A[Client] -->|로그인 요청| B[Auth Server]
  B -->|JWT 발급| A
  A -->|Authorization: Bearer JWT| C[Resource Server]
  C -->|서명 검증 + 클레임 검사| BPK[Auth Server Public Key 또는 Shared Secret]
  C -->|응답| A

구성 요소 및 역할


✅ 주요 원리 & 작동 원리

JWT 생성과 검증 과정


📊 장점 & 단점

장점

구분항목설명
장점Stateless 구조서버 세션 상태 저장 불필요, 확장성 우수 (vaadata.com)
장점Interoperability다양한 플랫폼 간 인증 정보 공유 용이
장점Self-contained클레임에 모든 인증·권한 정보 포함, 별도 조회 불필요
장점CompactURL-safe, 크기가 작아 HTTP 헤더에 효율적

단점 및 해결책

구분항목설명해결책
단점Non-revocable만료 전 취소 어려움 (stateless 특성) (supertokens.com)짧은 access token TTL + 서버 저장형 refresh token 도입
단점키 탈취 위험비밀키 누출 시 위조 가능KID 기반 키 로테이션 및 HSM 활용
단점토큰 크기 제한페이로드 큰 경우 HTTP 헤더 제한 초과 가능포함 클레임 최소화
단점alg Attack 취약성악성 alg=none, 헤더 조작 등서버 측 알고리즘 고정 정책 및 검증 강화

🧩 분류 기준에 따른 유형

분류 기준유형설명
서명 방식JWS(Signature)HMAC/RS/ES 계열로 서명된 토큰
JWE(Encrypted)암호화 포함, 기밀성 보장 (curity.io)
대칭/비대칭 키HSxxx (대칭)하나의 secret 공유
RSxxx / ESxxx (비대칭)private/public key 분리
사용 목적Access Token리소스 접근 제어에 사용
Refresh TokenAccess 토큰 재발급 목적으로 opaque 형태 저장

🛠️ 실무 사용 & 구현 예시

사용 예시 표

시스템 구성목적효과
SPA + 백엔드 (REST API)로그인 → JWT 발급무상태 인증, 인증 부하 감소
마이크로서비스 아키텍처서비스 간 인증인증 서버 - 자원 서버 분리 및 확장성 개선
OAuth2 / OpenID ConnectSSO 구현ID Token 으로 사용자 정보 전달

활용 사례–마이크로서비스

With JWT Vs Without JWT


🧪 구현 예시 (Node.js–Express + jsonwebtoken)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const express = require('express');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

const SECRET = process.env.JWT_SECRET || 'supersecret';
const ACCESS_EXPIRES = '15m';
const REFRESH_EXPIRES = '7d';

// 로그인 엔드포인트
app.post('/login', (req, res) => {
  const {userId} = req.body;
  const accessToken = jwt.sign({ sub: userId, role: 'user' }, SECRET, { expiresIn: ACCESS_EXPIRES });
  const refreshToken = jwt.sign({ sub: userId }, SECRET, { expiresIn: REFRESH_EXPIRES });
  // 리프레시 토큰은 httpOnly 쿠키로 전송
  res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true });
  res.json({ accessToken });
});

// 보호된 자원
app.get('/profile', (req, res, next) => {
  const auth = req.headers.authorization;
  if (!auth) return res.status(401).end();
  const token = auth.split(' ')[1];
  try {
    const payload = jwt.verify(token, SECRET);
    res.json({ userId: payload.sub, role: payload.role });
  } catch {
    res.status(401).json({ error: 'Invalid or expired token' });
  }
});

// 리프레시 엔드포인트
app.post('/refresh', (req, res) => {
  const rt = req.cookies.refreshToken;
  try {
    const payload = jwt.verify(rt, SECRET);
    const newAccess = jwt.sign({ sub: payload.sub, role: 'user' }, SECRET, { expiresIn: ACCESS_EXPIRES });
    res.json({ accessToken: newAccess });
  } catch {
    res.status(403).end();
  }
});

app.listen(3000, ()=>console.log('Server running'));

요약

JWT 는 인증 및 권한 정보를 안전하게 전송하기 위한 편리하고 확장성있는 방식이나, 키 관리, 클레임 최소화, 토큰 만료/취소 전략, 암호화 여부 등을 신중하게 설계해야 합니다. 추가 다이어그램, 최적화, 보안 전략 등이 필요하시면 언제든 요청 바랍니다 😊


🛑 단점과 문제점 & 해결방안

단점

구분항목설명해결책
단점Non‑revocable (취소 불가능)Stateless 특성으로 access token 은 취소 어려움짧은 TTL 설정 + 서버 저장형 refresh token 사용
토큰 탈취 위험브라우저 저장소 노출 가능성 → 탈취 시 모든 서비스 접근 가능httpOnly 쿠키, TLS 전송, CORS 설정 강화
키 관리 복잡성키 교체 시 기존 토큰 유효성 문제 발생키 ID(KID) 포함 및 키 로테이션 정책 수립
alg 공격 취약성alg=none, 헤더 조작 등 공격에 노출 가능알고리즘 고정, 라이브러리 검증 강화, alg 검증 로직 구현

문제점 (심화 분석)

구분항목원인영향탐지 및 진단예방 방법해결 방법 및 기법
문제점중간자 (MITM) 공격TLS 미사용 또는 취약한 HTTPS 구성토큰 유출 및 변조 가능HTTPS 검사, 패킷 분석 경보TLS 전송 강제, HSTS 적용모든 클라이언트 통신 HTTPS 로 전환
탈취된 refresh token 재사용refresh token 유효기간 또는 동시사용 허용사용자 계정 탈취 및 장기 접근 가능비정상 refresh 기록 모니터링refresh token 회전 (rotation), 단회성토큰 사용 시마다 새 refresh 발급 및 이전 만료
리플레이 공격동일 token 반복 사용권한 없는 요청 수행로그 분석, 요청 재사용 탐지Nonce 나 jti 활용토큰에 jti, nonce 포함 및 서버 블랙리스트
클레임 변조 공격header/payload 위조 시 검증 소홀권한 우회 및 데이터 위변조이상 payload 탐지 시스템서명 검증 강화서명 이후 payload 검사 실패 시 즉시 거부

🎯 도전 과제 (Challenges)


🧰 실무 적용 고려사항 및 주의사항

카테고리주의할 점/고려사항권장사항
토큰 저장소XSS/CSRF 취약점httpOnly + sameSite 쿠키 사용
전송 보안URI 전달, 로컬스토리지 사용 지양Authorization 헤더로 전달, HTTPS 적용
클레임 설계민감정보 포함 방지사용자 ID 정도만 포함하고 리소스 서버 조회
키 관리 및 회전비대칭 키 사용, 로테이션 계획KID 포함, 공개 키 발행 · 삭제 정책 수립
만료 시간 설정너무 길거나 짧은 만료 불균형access: 5–15 분 / refresh: 7–30 일
재발급 전략 설계무한 루프 갱신 등 위험 방지토큰 회전 및 백리스트 (blacklist) 설계
로깅/모니터링이상 트래픽·토큰 재사용 탐지 중요모든 발급/사용 이벤트 로깅
에러 핸들링잘못된 토큰 요청에 과도한 정보 노출 주의일반 메시지만 노출 (Server error 등)
라이브러리 선택취약점 있는 구현 주의 (alg=none 등)검증된 공식 라이브러리 사용

⚙️ 최적화 고려사항 및 권장사항

카테고리최적화 요소권장 방법
토큰 크기페이로드 최소화필수 클레임만 포함 (예: sub, exp)
서명 알고리즘속도 vs 안정성 균형HS256 속도 / RS256 보안 선택
키 로테이션이전 토큰 여파 최소화KID 와 함께 주기적 교체
백엔드 검증불필요한 검증 최소한 수행필요한 경우만 서명 + 클레임 검증
캐싱공개키 캐싱JWKS, 메모리 캐싱으로 네트워크 최소화
모니터링서명 실패/재사용 알림알림 시스템 설정
배치 처리만료된 토큰 정리 자동화백그라운드 스크립트 주기 실행

📌 마무리 요약

JWT 는 클라우드, 마이크로서비스, SPA 환경에서 무상태 인증과 스케일 아웃을 가능하게 하는 핵심 기술입니다. 그러나 키 관리, 토큰 취소 및 보안 취약성을 고려해야 하며, 이를 위해 권장 라이브러리 사용, 키 로테이션, 짧은 TTL, cookie 기반 저장, 검증 강화를 포함한 설계가 필요합니다.


📚 용어 정리

카테고리용어설명
보안XSS/CSRF크로스 사이트 스크립팅 / 요청 위조 공격
토큰 관리jti, nonceJWT 고유 식별자/Replay 공격 방지용 임의값
키 운영KID, JWKS키 식별자와 JSON Web Key Set
토큰 유형Access vs Refresh Token접근과 재발급 목적으로 분리된 토큰 종류

원하시면 mermaid 기반의 실무 구조 다이어그램, Python 구현 예제, 혹은 최적화된 인증 서버 설계 등에 대해 더 분석해 드릴 수 있습니다. 언제든지 말씀해주세요!


JWT 에 대한 포괄적인 조사를 수행하겠습니다. 먼저 최신 정보를 확인하기 위해 웹 검색을 실시하겠습니다.

JWT (JSON Web Token) 포괄적 분석 및 조사

1. 태그

2. 주제의 분류 계층 구조 검증

요청된 분류: “Computer Science and Engineering > Cybersecurity and Information Security > Access Control > Implementations > Token-Based Authentication”

검증 결과: 이 계층 구조는 적절합니다. JWT 는 근본적으로 토큰 기반 인증의 구현체로서, 접근 제어 메커니즘의 일부이며, 정보 보안 영역에 속하는 컴퓨터 과학 기술입니다. 이는 JWT 의 본질적 특성과 활용 목적에 정확히 부합합니다.

3. 주제 요약 (200 자 내외)

JWT 는 RFC 7519 표준에 따른 JSON 기반 보안 토큰으로, 당사자 간 클레임을 안전하게 전송하는 컴팩트하고 자체 포함된 방식입니다. 디지털 서명이나 암호화를 통해 무결성과 인증을 보장하며, 상태 비저장 인증 메커니즘으로 현대 웹 애플리케이션과 API 에서 널리 사용됩니다.

4. 전체 개요 (250 자 내외)

본 분석은 JWT 의 핵심 개념부터 실무 적용까지 포괄적으로 다룹니다. JWT 의 구조 (헤더, 페이로드, 서명), 작동 원리, 주요 알고리즘 (HS256, RS256), 보안 고려사항, 장단점, 구현 기법, 활용 사례, 최적화 방안을 심도 있게 분석합니다. 특히 실무에서의 효과적인 적용과 보안 취약점 대응 방안을 중점적으로 제시합니다.


1 부: 기본 개념 및 이론

핵심 개념

JWT 는 JSON Web Token 의 약자로, **JSON 객체로 정보를 안전하게 전송하기 위한 개방형 표준 (RFC 7519)**입니다. 다음과 같은 핵심 개념들을 포함합니다:

기본 정의

JSON Object Signing and Encryption (JOSE) 연관성

실무에서 구현하기 위한 필요 내용들

  1. 알고리즘 선택: HS256(대칭키) vs RS256(비대칭키) 선택 기준
  2. 클레임 설계: 표준 클레임 (iss, sub, aud, exp, nbf, iat, jti) 과 커스텀 클레임 활용
  3. 키 관리: 서명 키의 안전한 저장, 순환, 배포 메커니즘
  4. 토큰 라이프사이클: 발급, 검증, 갱신, 폐기 과정
  5. 보안 고려사항: 토큰 저장 위치, 전송 보안, 만료 시간 설정

배경

JWT 의 등장 배경은 다음과 같습니다:

기존 세션 기반 인증의 한계

모바일 및 SPA 애플리케이션의 증가

표준화 요구

목적 및 필요성

주요 목적

  1. 상태 비저장 인증: 서버 확장성 향상
  2. 정보 전달: 안전한 클레임 전송
  3. 분산 시스템 지원: 마이크로서비스 간 인증 정보 공유
  4. 표준화: 다양한 플랫폼 간 호환성 제공

필요성

주요 기능 및 역할

인증 (Authentication)

인가 (Authorization)

정보 교환 (Information Exchange)

Single Sign-On (SSO)

특징

구조적 특징

보안적 특징

운영적 특징


2 부: 구조 및 기술적 분석

주요 원리

JWT 의 주요 원리는 다음과 같습니다:

무결성 보장 원리

  1. 해시 기반 서명: 헤더와 페이로드의 해시값을 비밀키로 서명
  2. 변조 감지: 토큰 내용 변경 시 서명 불일치로 감지
  3. 원본 보장: 서명 검증을 통한 데이터 무결성 확인

인증 원리

  1. 신원 확인: 서명자의 개인키 소유 증명
  2. 부인 방지: 비대칭키 사용 시 서명자 부인 불가
  3. 신뢰 체인: 공개키 인프라를 통한 신뢰성 확보

작동 원리

JWT 의 작동 원리를 다이어그램으로 표현하면 다음과 같습니다:

sequenceDiagram
    participant Client as 클라이언트
    participant AuthServer as 인증 서버
    participant ResourceServer as 리소스 서버
    
    Client->>AuthServer: 1. 로그인 요청 (credentials)
    AuthServer->>AuthServer: 2. 자격 증명 검증
    AuthServer->>AuthServer: 3. JWT 생성 및 서명
    AuthServer->>Client: 4. JWT 반환
    Client->>ResourceServer: 5. 리소스 요청 + JWT
    ResourceServer->>ResourceServer: 6. JWT 서명 검증
    ResourceServer->>ResourceServer: 7. 클레임 확인
    ResourceServer->>Client: 8. 리소스 응답

상세 작동 과정

  1. 토큰 생성 단계

    • 사용자 인증 후 서버에서 JWT 생성
    • 헤더, 페이로드 정보 설정
    • 비밀키 또는 개인키로 서명 생성
  2. 토큰 전송 단계

    • Authorization 헤더에 Bearer 토큰으로 전송
    • Authorization: Bearer <JWT>
  3. 토큰 검증 단계

    • 서버에서 토큰 수신
    • 서명 검증을 통한 무결성 확인
    • 클레임 검증 (만료 시간, 발급자 등)
  4. 접근 제어 단계

    • 유효한 토큰의 경우 리소스 접근 허용
    • 무효한 토큰의 경우 접근 거부

구조 및 아키텍처

JWT 는 다음과 같은 3 부분 구조를 가집니다:

1
Header.Payload.Signature

구조 다이어그램

graph TD
    A[JWT Token] --> B[Header]
    A --> C[Payload]
    A --> D[Signature]
    
    B --> B1[alg: 알고리즘]
    B --> B2[typ: 토큰 타입]
    B --> B3[kid: 키 ID]
    
    C --> C1[Standard Claims]
    C --> C2[Public Claims]
    C --> C3[Private Claims]
    
    C1 --> C11[iss: 발급자]
    C1 --> C12[sub: 주체]
    C1 --> C13[aud: 대상]
    C1 --> C14[exp: 만료시간]
    C1 --> C15[nbf: 이전 무효]
    C1 --> C16[iat: 발급시간]
    C1 --> C17[jti: JWT ID]
    
    D --> D1[HMAC 서명]
    D --> D2[RSA 서명]
    D --> D3[ECDSA 서명]

구성 요소

필수 구성요소

1. 헤더 (Header)

1
2
3
4
{
  "alg": "HS256",
  "typ": "JWT"
}

2. 페이로드 (Payload)

1
2
3
4
5
6
7
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "exp": 1516325422
}

3. 서명 (Signature)

선택 구성요소

1. 암호화 (JWE)

2. 중첩 구조

구현 기법

1. 대칭키 알고리즘 (HMAC)

정의: 하나의 비밀키를 사용하여 서명 생성 및 검증

구성

목적: 단일 서비스 내에서의 빠른 토큰 처리

실제 예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import hmac
import hashlib
import base64

def create_signature(header, payload, secret):
    message = f"{header}.{payload}"
    signature = hmac.new(
        secret.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha256
    ).digest()
    return base64.urlsafe_b64encode(signature).decode('utf-8').rstrip('=')

시스템 구성: 단일 애플리케이션 서버에서 토큰 발급 및 검증

2. 비대칭키 알고리즘 (RSA/ECDSA)

정의: 공개키/개인키 쌍을 사용하여 서명 생성 및 검증

구성

목적: 분산 시스템에서의 안전한 토큰 검증

실제 예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const jwt = require('jsonwebtoken');
const fs = require('fs');

// 개인키로 서명
const privateKey = fs.readFileSync('private.pem');
const token = jwt.sign({ userId: 123 }, privateKey, { algorithm: 'RS256' });

// 공개키로 검증
const publicKey = fs.readFileSync('public.pem');
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });

시스템 구성: 인증 서버 (개인키) 와 리소스 서버 (공개키) 분리

3. 토큰 리프레시 메커니즘

정의: 액세스 토큰과 리프레시 토큰을 조합한 보안 강화 기법

구성

목적: 보안성과 사용성의 균형

시나리오

  1. 초기 로그인 시 두 토큰 발급
  2. 액세스 토큰 만료 시 리프레시 토큰으로 갱신
  3. 리프레시 토큰도 순환하여 보안 강화

3 부: 장단점 및 문제점 분석

장점

구분항목설명
장점확장성 (Scalability)서버에서 세션 상태를 저장하지 않아 수평 확장이 용이하며, 로드 밸런서 사용 시 세션 고정 (sticky session) 불필요
장점성능 (Performance)토큰 자체에 정보가 포함되어 데이터베이스 조회 없이 빠른 인증 처리 가능
장점상호 운용성 (Interoperability)표준화된 JSON 형식으로 다양한 언어와 플랫폼에서 호환
장점무결성 (Integrity)디지털 서명을 통해 토큰 변조 감지 및 방지
장점분산 시스템 지원마이크로서비스 아키텍처에서 서비스 간 인증 정보 공유 용이
장점모바일 친화적쿠키에 의존하지 않아 모바일 애플리케이션에서 활용 용이
장점CORS 해결크로스 도메인 요청 시 쿠키 제한 없이 인증 헤더 사용 가능

단점과 문제점 그리고 해결방안

단점
구분항목설명해결책
단점토큰 크기쿠키 세션 ID 보다 크기가 크며, 많은 클레임 포함 시 네트워크 오버헤드 증가필요한 클레임만 포함, 토큰 압축, Reference Token 사용 고려
단점즉시 무효화 불가만료 전까지는 서버에서 강제 무효화가 어려움블랙리스트 관리, 짧은 만료 시간 + 리프레시 토큰, Redis 기반 토큰 저장소 활용
단점정보 노출Base64 인코딩으로 페이로드 내용이 쉽게 디코딩 가능민감한 정보는 토큰에 포함하지 않음, JWE 암호화 사용
단점키 관리 복잡성서명키 보안 관리 및 순환의 복잡성HSM 사용, 키 관리 서비스 활용, 정기적 키 순환 정책 수립
문제점
구분항목원인영향탐지 및 진단예방 방법해결 방법 및 기법
문제점Algorithm Confusionnone 알고리즘 허용 또는 알고리즘 검증 부족서명 우회 공격 가능토큰 헤더의 alg 필드 모니터링허용 알고리즘 화이트리스트 적용강제 알고리즘 지정, none 알고리즘 차단
문제점Key Confusion공개키를 대칭키로 오인하여 사용키 추측 공격 취약성알고리즘과 키 타입 불일치 모니터링알고리즘별 키 타입 강제 검증키 타입과 알고리즘 매칭 검증 로직 구현
문제점Token LeakageXSS, 로그 노출, 네트워크 스니핑무단 접근 및 권한 상승비정상적인 토큰 사용 패턴 모니터링HTTPS 강제, 안전한 저장소 사용, CSP 적용토큰 순환, 짧은 만료 시간, 위치 기반 검증
문제점Weak Secret짧거나 예측 가능한 비밀키 사용브루트포스 공격으로 키 탈취무결성 실패 토큰 급증 모니터링충분한 엔트로피의 키 생성강력한 키 생성, 정기적 키 순환

도전 과제

보안 과제

1. 고급 지속 위협 (APT) 대응

2. 양자 컴퓨팅 위협

운영 과제

1. 대규모 토큰 관리

2. 규정 준수

기술 과제

1. 엣지 컴퓨팅 통합

2. 실시간 토큰 무효화


4 부: 실무 적용 및 최적화

분류 기준에 따른 종류 및 유형

분류 기준종류/유형특징사용 사례
서명 방식JWS (Signed)무결성 보장, 내용 가시일반적인 인증/인가
서명 방식JWE (Encrypted)기밀성 보장, 내용 암호화민감한 정보 전송
서명 방식Nested JWT서명 후 암호화최고 보안 요구사항
알고리즘대칭키 (HS256/384/512)빠른 처리, 단일 키단일 서비스 환경
알고리즘비대칭키 (RS256/384/512)키 분리, 확장성분산 시스템
알고리즘타원곡선 (ES256/384/512)작은 키 크기, 효율성모바일/IoT 환경
용도Access Token단기간, API 접근RESTful API 인증
용도Refresh Token장기간, 토큰 갱신토큰 재발급
용도ID Token사용자 정보, OpenID ConnectSSO, 사용자 프로필
저장 위치메모리 저장휘발성, 보안성 높음SPA 애플리케이션
저장 위치localStorage지속성, XSS 취약편의성 우선 환경
저장 위치httpOnly CookieXSS 방어, CSRF 주의전통적 웹 애플리케이션

실무 사용 예시

분야사용 목적함께 사용되는 기술효과
마이크로서비스서비스 간 인증Spring Security, Kubernetes중앙 집중식 인증, 서비스 메시 보안
모바일 앱사용자 인증React Native, Flutter오프라인 동작, 빠른 로그인
SPA 웹앱상태 유지React, Vue.js, Angular새로고침 시 로그인 유지
API Gateway접근 제어Kong, AWS API Gateway통합 인증, 요청 필터링
IoT 시스템디바이스 인증MQTT, CoAP경량 인증, 배터리 효율성
금융 시스템거래 인증OAuth 2.0, OpenID Connect높은 보안, 규정 준수

활용 사례: 대규모 이커머스 플랫폼

시스템 구성
graph TB
    subgraph "클라이언트 계층"
        A[웹 브라우저]
        B[모바일 앱]
        C[파트너 API]
    end
    
    subgraph "API Gateway 계층"
        D[AWS API Gateway]
        E[JWT 검증 Lambda]
    end
    
    subgraph "마이크로서비스 계층"
        F[사용자 서비스]
        G[상품 서비스]
        H[주문 서비스]
        I[결제 서비스]
    end
    
    subgraph "인증 계층"
        J[Auth0 서비스]
        K[Redis Token Store]
    end
    
    subgraph "데이터 계층"
        L[사용자 DB]
        M[상품 DB]
        N[주문 DB]
    end
    
    A --> D
    B --> D
    C --> D
    D --> E
    E --> F
    E --> G
    E --> H
    E --> I
    
    F --> J
    G --> J
    H --> J
    I --> J
    
    J --> K
    J --> L
    
    F --> L
    G --> M
    H --> N
    I --> N
워크플로우
  1. 사용자 로그인

    • 클라이언트에서 Auth0 서비스로 인증 요청
    • 성공 시 Access Token(15 분) + Refresh Token(7 일) 발급
    • 토큰을 httpOnly 쿠키에 저장
  2. API 요청 처리

    • API Gateway 에서 JWT 검증 Lambda 실행
    • 토큰 서명 검증 및 만료 시간 확인
    • Redis 에서 블랙리스트 확인
  3. 마이크로서비스 호출

    • 검증된 사용자 정보를 헤더에 포함하여 서비스 호출
    • 각 서비스에서 권한 기반 접근 제어 수행
JWT 의 역할
JWT 유무에 따른 차이점

JWT 사용 시

세션 기반 인증 시

구현 예시

다음은 Python 과 JavaScript 를 사용한 JWT 구현 예시입니다:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
/**
 * JWT (JSON Web Token) 구현 예시 - JavaScript (Node.js)
 * 대규모 이커머스 플랫폼의 사용자 인증 및 권한 관리 시스템
 */

const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const redis = require('redis');
const fs = require('fs');

class JWTManager {
    /**
     * JWT 매니저 클래스 생성자
     * @param {Object} options - 설정 옵션
     */
    constructor(options = {}) {
        this.algorithm = 'RS256'; // 비대칭 알고리즘 사용
        this.accessTokenExpireMinutes = 15; // 액세스 토큰 만료: 15분
        this.refreshTokenExpireDays = 7; // 리프레시 토큰 만료: 7일
        
        // Redis 클라이언트 초기화 (블랙리스트 관리용)
        this.redisClient = redis.createClient({
            host: options.redisHost || 'localhost',
            port: options.redisPort || 6379
        });
        
        this.redisClient.on('error', (err) => {
            console.error('Redis 연결 오류:', err);
        });
        
        // RSA 키 쌍 로드 또는 생성
        this.loadOrGenerateKeys(options.privateKeyPath, options.publicKeyPath);
    }
    
    /**
     * RSA 키 쌍 로드 또는 생성
     * @param {string} privateKeyPath - 개인키 파일 경로
     * @param {string} publicKeyPath - 공개키 파일 경로
     */
    loadOrGenerateKeys(privateKeyPath, publicKeyPath) {
        try {
            if (privateKeyPath && publicKeyPath && 
                fs.existsSync(privateKeyPath) && fs.existsSync(publicKeyPath)) {
                // 기존 키 로드
                this.privateKey = fs.readFileSync(privateKeyPath, 'utf8');
                this.publicKey = fs.readFileSync(publicKeyPath, 'utf8');
                console.log('기존 RSA 키 쌍 로드 완료');
            } else {
                // 새로운 키 생성 (개발/테스트용)
                this.generateKeys();
            }
        } catch (error) {
            console.error('키 로드 실패:', error);
            this.generateKeys();
        }
    }
    
    /**
     * 새로운 RSA 키 쌍 생성 (개발/테스트용)
     */
    generateKeys() {
        console.log('새로운 RSA 키 쌍 생성 중…');
        const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
            modulusLength: 2048,
            publicKeyEncoding: {
                type: 'spki',
                format: 'pem'
            },
            privateKeyEncoding: {
                type: 'pkcs8',
                format: 'pem'
            }
        });
        
        this.privateKey = privateKey;
        this.publicKey = publicKey;
        console.log('RSA 키 쌍 생성 완료');
    }
    
    /**
     * 액세스 토큰 생성
     * @param {string} userId - 사용자 ID
     * @param {string} username - 사용자명
     * @param {Array} roles - 사용자 역할 목록
     * @param {Array} permissions - 사용자 권한 목록
     * @returns {string} 생성된 JWT 액세스 토큰
     */
    createAccessToken(userId, username, roles = [], permissions = []) {
        const now = Math.floor(Date.now() / 1000);
        const exp = now + (this.accessTokenExpireMinutes * 60);
        
        // JWT 클레임 구성
        const payload = {
            sub: userId,                     // Subject (사용자 ID)
            username: username,              // 사용자명
            roles: roles,                    // 역할 목록
            permissions: permissions,        // 권한 목록
            iss: 'ecommerce-platform',      // Issuer (발급자)
            aud: 'ecommerce-api',           // Audience (대상)
            iat: now,                       // Issued At (발급 시간)
            exp: exp,                       // Expiration Time (만료 시간)
            jti: this.generateJTI()         // JWT ID (고유 식별자)
        };
        
        // 토큰 생성 및 서명
        const token = jwt.sign(payload, this.privateKey, {
            algorithm: this.algorithm,
            header: {
                kid: 'main-signing-key' // Key ID
            }
        });
        
        return token;
    }
    
    /**
     * 리프레시 토큰 생성
     * @param {string} userId - 사용자 ID
     * @returns {string} 생성된 JWT 리프레시 토큰
     */
    createRefreshToken(userId) {
        const now = Math.floor(Date.now() / 1000);
        const exp = now + (this.refreshTokenExpireDays * 24 * 60 * 60);
        
        const payload = {
            sub: userId,
            type: 'refresh',                // 토큰 타입 명시
            iss: 'ecommerce-platform',
            aud: 'ecommerce-auth',
            iat: now,
            exp: exp,
            jti: this.generateJTI()
        };
        
        return jwt.sign(payload, this.privateKey, {
            algorithm: this.algorithm
        });
    }
    
    /**
     * 토큰 검증
     * @param {string} token - 검증할 JWT 토큰
     * @returns {Promise<Object|null>} 검증 성공 시 페이로드, 실패 시 null
     */
    async verifyToken(token) {
        try {
            // 블랙리스트 확인
            const isBlacklisted = await this.isBlacklisted(token);
            if (isBlacklisted) {
                console.log('블랙리스트에 등록된 토큰입니다.');
                return null;
            }
            
            // 토큰 디코딩 및 검증
            const payload = jwt.verify(token, this.publicKey, {
                algorithms: [this.algorithm],
                audience: 'ecommerce-api',
                issuer: 'ecommerce-platform'
            });
            
            return payload;
            
        } catch (error) {
            if (error.name === 'TokenExpiredError') {
                console.log('토큰이 만료되었습니다.');
            } else if (error.name === 'JsonWebTokenError') {
                console.log('유효하지 않은 토큰입니다:', error.message);
            } else {
                console.log('토큰 검증 중 오류:', error);
            }
            return null;
        }
    }
    
    /**
     * 리프레시 토큰을 사용하여 새로운 액세스 토큰 생성
     * @param {string} refreshToken - 리프레시 토큰
     * @returns {Promise<string|null>} 새로운 액세스 토큰 또는 null
     */
    async refreshAccessToken(refreshToken) {
        try {
            // 리프레시 토큰 검증
            const payload = jwt.verify(refreshToken, this.publicKey, {
                algorithms: [this.algorithm],
                audience: 'ecommerce-auth',
                issuer: 'ecommerce-platform'
            });
            
            if (payload.type !== 'refresh') {
                console.log('리프레시 토큰이 아닙니다.');
                return null;
            }
            
            const userId = payload.sub;
            if (!userId) {
                console.log('사용자 ID가 없습니다.');
                return null;
            }
            
            // 실제 환경에서는 DB에서 사용자 정보 조회
            const userInfo = await this.getUserInfo(userId);
            if (!userInfo) {
                console.log('사용자 정보를 찾을 수 없습니다.');
                return null;
            }
            
            // 새로운 액세스 토큰 생성
            const newAccessToken = this.createAccessToken(
                userId,
                userInfo.username,
                userInfo.roles,
                userInfo.permissions
            );
            
            return newAccessToken;
            
        } catch (error) {
            if (error.name === 'TokenExpiredError') {
                console.log('리프레시 토큰이 만료되었습니다.');
            } else {
                console.log('유효하지 않은 리프레시 토큰입니다:', error.message);
            }
            return null;
        }
    }
    
    /**
     * 토큰을 블랙리스트에 추가
     * @param {string} token - 블랙리스트에 추가할 토큰
     */
    async blacklistToken(token) {
        try {
            // 토큰의 jti 추출 (서명 검증 없이)
            const unverifiedPayload = jwt.decode(token);
            const jti = unverifiedPayload?.jti;
            const exp = unverifiedPayload?.exp;
            
            if (jti && exp) {
                // 만료 시간까지만 블랙리스트에 보관
                const ttl = exp - Math.floor(Date.now() / 1000);
                if (ttl > 0) {
                    await this.redisClient.setEx(`blacklist:${jti}`, ttl, 'true');
                    console.log(`토큰이 블랙리스트에 추가되었습니다: ${jti}`);
                }
            }
        } catch (error) {
            console.error('블랙리스트 추가 중 오류:', error);
        }
    }
    
    /**
     * 토큰이 블랙리스트에 있는지 확인
     * @param {string} token - 확인할 토큰
     * @returns {Promise<boolean>} 블랙리스트 여부
     */
    async isBlacklisted(token) {
        try {
            const unverifiedPayload = jwt.decode(token);
            const jti = unverifiedPayload?.jti;
            
            if (jti) {
                const exists = await this.redisClient.exists(`blacklist:${jti}`);
                return exists === 1;
            }
            
            return false;
        } catch (error) {
            return false;
        }
    }
    
    /**
     * JWT ID 생성
     * @returns {string} 고유한 JWT ID
     */
    generateJTI() {
        return crypto.randomBytes(32).toString('hex');
    }
    
    /**
     * 사용자 정보 조회 (실제 환경에서는 DB 연동)
     * @param {string} userId - 사용자 ID
     * @returns {Promise<Object|null>} 사용자 정보
     */
    async getUserInfo(userId) {
        // 실제 환경에서는 데이터베이스에서 조회
        const mockUsers = {
            'user123': {
                username: 'john_doe',
                roles: ['customer', 'premium'],
                permissions: ['read:products', 'write:orders', 'read:profile']
            },
            'admin456': {
                username: 'admin_user',
                roles: ['admin'],
                permissions: ['read:*', 'write:*', 'delete:*']
            }
        };
        
        return mockUsers[userId] || null;
    }
}

/**
 * 인증 미들웨어 클래스
 */
class AuthMiddleware {
    /**
     * 인증 미들웨어 생성자
     * @param {JWTManager} jwtManager - JWT 매니저 인스턴스
     */
    constructor(jwtManager) {
        this.jwtManager = jwtManager;
    }
    
    /**
     * Express.js 미들웨어 함수
     * @returns {Function} Express 미들웨어 함수
     */
    middleware() {
        return async (req, res, next) => {
            try {
                const authHeader = req.headers.authorization;
                const userInfo = await this.authenticate(authHeader);
                
                if (userInfo) {
                    req.user = userInfo;
                    next();
                } else {
                    res.status(401).json({
                        error: 'Unauthorized',
                        message: '유효한 인증 토큰이 필요합니다.'
                    });
                }
            } catch (error) {
                console.error('인증 미들웨어 오류:', error);
                res.status(500).json({
                    error: 'Internal Server Error',
                    message: '서버 내부 오류가 발생했습니다.'
                });
            }
        };
    }
    
    /**
     * Authorization 헤더를 통한 인증
     * @param {string} authorizationHeader - Authorization 헤더 값
     * @returns {Promise<Object|null>} 인증 성공 시 사용자 정보, 실패 시 null
     */
    async authenticate(authorizationHeader) {
        if (!authorizationHeader) {
            return null;
        }
        
        // Bearer 토큰 추출
        const parts = authorizationHeader.split(' ');
        if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
            return null;
        }
        
        const token = parts[1];
        const payload = await this.jwtManager.verifyToken(token);
        
        if (payload) {
            return {
                userId: payload.sub,
                username: payload.username,
                roles: payload.roles || [],
                permissions: payload.permissions || []
            };
        }
        
        return null;
    }
    
    /**
     * 권한 확인 미들웨어
     * @param {string} requiredPermission - 필요한 권한
     * @returns {Function} Express 미들웨어 함수
     */
    requirePermission(requiredPermission) {
        return (req, res, next) => {
            if (!req.user) {
                return res.status(401).json({
                    error: 'Unauthorized',
                    message: '인증이 필요합니다.'
                });
            }
            
            if (this.hasPermission(req.user, requiredPermission)) {
                next();
            } else {
                res.status(403).json({
                    error: 'Forbidden',
                    message: '권한이 부족합니다.'
                });
            }
        };
    }
    
    /**
     * 권한 확인
     * @param {Object} userInfo - 사용자 정보
     * @param {string} requiredPermission - 필요한 권한
     * @returns {boolean} 권한 보유 여부
     */
    hasPermission(userInfo, requiredPermission) {
        const userPermissions = userInfo.permissions || [];
        
        // 와일드카드 권한 확인
        if (userPermissions.includes('read:*') || userPermissions.includes('write:*')) {
            return true;
        }
        
        // 구체적 권한 확인
        return userPermissions.includes(requiredPermission);
    }
    
    /**
     * 역할 확인 미들웨어
     * @param {string} requiredRole - 필요한 역할
     * @returns {Function} Express 미들웨어 함수
     */
    requireRole(requiredRole) {
        return (req, res, next) => {
            if (!req.user) {
                return res.status(401).json({
                    error: 'Unauthorized',
                    message: '인증이 필요합니다.'
                });
            }
            
            const userRoles = req.user.roles || [];
            if (userRoles.includes(requiredRole) || userRoles.includes('admin')) {
                next();
            } else {
                res.status(403).json({
                    error: 'Forbidden',
                    message: '필요한 역할이 없습니다.'
                });
            }
        };
    }
}

/**
 * Express.js 애플리케이션 예시
 */
const express = require('express');

async function createApp() {
    const app = express();
    app.use(express.json());
    
    // JWT 매니저 및 인증 미들웨어 초기화
    const jwtManager = new JWTManager();
    const authMiddleware = new AuthMiddleware(jwtManager);
    
    // 로그인 엔드포인트
    app.post('/auth/login', async (req, res) => {
        try {
            const { username, password } = req.body;
            
            // 실제 환경에서는 DB에서 사용자 검증
            if (username === 'john_doe' && password === 'password123') {
                const accessToken = jwtManager.createAccessToken(
                    'user123',
                    'john_doe',
                    ['customer', 'premium'],
                    ['read:products', 'write:orders', 'read:profile']
                );
                const refreshToken = jwtManager.createRefreshToken('user123');
                
                res.json({
                    accessToken,
                    refreshToken,
                    expiresIn: jwtManager.accessTokenExpireMinutes * 60
                });
            } else {
                res.status(401).json({
                    error: 'Invalid credentials'
                });
            }
        } catch (error) {
            console.error('로그인 오류:', error);
            res.status(500).json({
                error: 'Internal server error'
            });
        }
    });
    
    // 토큰 갱신 엔드포인트
    app.post('/auth/refresh', async (req, res) => {
        try {
            const { refreshToken } = req.body;
            
            if (!refreshToken) {
                return res.status(400).json({
                    error: 'Refresh token is required'
                });
            }
            
            const newAccessToken = await jwtManager.refreshAccessToken(refreshToken);
            
            if (newAccessToken) {
                res.json({
                    accessToken: newAccessToken,
                    expiresIn: jwtManager.accessTokenExpireMinutes * 60
                });
            } else {
                res.status(401).json({
                    error: 'Invalid refresh token'
                });
            }
        } catch (error) {
            console.error('토큰 갱신 오류:', error);
            res.status(500).json({
                error: 'Internal server error'
            });
        }
    });
    
    // 로그아웃 엔드포인트
    app.post('/auth/logout', authMiddleware.middleware(), async (req, res) => {
        try {
            const authHeader = req.headers.authorization;
            const token = authHeader.split(' ')[1];
            
            await jwtManager.blacklistToken(token);
            
            res.json({
                message: 'Successfully logged out'
            });
        } catch (error) {
            console.error('로그아웃 오류:', error);
            res.status(500).json({
                error: 'Internal server error'
            });
        }
    });
    
    // 보호된 리소스 - 상품 목록 조회
    app.get('/api/products', 
        authMiddleware.middleware(),
        authMiddleware.requirePermission('read:products'),
        (req, res) => {
            res.json({
                message: '상품 목록 조회 성공',
                user: req.user,
                products: [
                    { id: 1, name: 'Laptop', price: 1000 },
                    { id: 2, name: 'Mouse', price: 20 }
                ]
            });
        }
    );
    
    // 보호된 리소스 - 주문 생성
    app.post('/api/orders',
        authMiddleware.middleware(),
        authMiddleware.requirePermission('write:orders'),
        (req, res) => {
            res.json({
                message: '주문 생성 성공',
                user: req.user,
                orderId: Math.random().toString(36).substr(2, 9)
            });
        }
    );
    
    // 관리자 전용 엔드포인트
    app.get('/api/admin/users',
        authMiddleware.middleware(),
        authMiddleware.requireRole('admin'),
        (req, res) => {
            res.json({
                message: '사용자 목록 조회 성공',
                users: [
                    { id: 'user123', username: 'john_doe' },
                    { id: 'admin456', username: 'admin_user' }
                ]
            });
        }
    );
    
    return app;
}

// 사용 예시
async function main() {
    try {
        const app = await createApp();
        const PORT = process.env.PORT || 3000;
        
        app.listen(PORT, () => {
            console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`);
            console.log('\n=== API 사용 예시 ===');
            console.log('1. 로그인: POST /auth/login');
            console.log('   Body: {"username": "john_doe", "password": "password123"}');
            console.log('2. 상품 조회: GET /api/products');
            console.log('   Header: Authorization: Bearer <access_token>');
            console.log('3. 주문 생성: POST /api/orders');
            console.log('   Header: Authorization: Bearer <access_token>');
            console.log('4. 토큰 갱신: POST /auth/refresh');
            console.log('   Body: {"refreshToken": "<refresh_token>"}');
            console.log('5. 로그아웃: POST /auth/logout');
            console.log('   Header: Authorization: Bearer <access_token>');
        });
        
    } catch (error) {
        console.error('서버 시작 오류:', error);
    }
}

// 스크립트 직접 실행 시
if (require.main === module) {
    main();
}

module.exports = {
    JWTManager,
    AuthMiddleware,
    createApp
};

실무에서 효과적으로 적용하기 위한 고려사항 및 주의할 점

구분고려사항세부 내용권장사항
보안알고리즘 선택HS256 vs RS256 선택 기준분산 환경에서는 RS256, 단일 서비스는 HS256
보안키 관리서명키 보안 저장 및 순환HSM 사용, 정기적 키 순환 (90 일 주기)
보안토큰 저장클라이언트 측 안전한 저장httpOnly 쿠키 또는 메모리 저장 선호
성능토큰 크기페이로드 크기 최적화필수 클레임만 포함, 압축 고려
성능캐싱 전략공개키 캐싱 및 검증 최적화Redis 기반 키 캐싱, TTL 설정
운영만료 시간적절한 토큰 수명 설정액세스 토큰: 15 분 -1 시간, 리프레시: 7-30 일
운영모니터링토큰 사용 패턴 모니터링비정상 토큰 사용 감지, 로깅 시스템 구축
확장성상태 관리무효화 메커니즘 설계블랙리스트 + 짧은 만료 시간 조합

최적화하기 위한 고려사항 및 주의할 점

구분최적화 영역세부 방법권장사항
성능서명 검증알고리즘별 성능 차이 고려ECDSA > RSA > HMAC 순서로 검토
성능메모리 사용토큰 캐싱 전략LRU 캐시 + TTL 기반 자동 정리
성능네트워크토큰 크기 최소화불필요한 클레임 제거, 압축 적용
보안엔트로피강력한 키 생성최소 256 비트 엔트로피, CSPRNG 사용
보안검증 로직클레임 검증 최적화필수 클레임 우선 검증, 조기 반환
확장성분산 처리키 배포 메커니즘JWKS 엔드포인트, CDN 활용
확장성로드 밸런싱무상태성 활용세션 고정 없는 로드 밸런싱
유지보수버전 관리키 버전 관리 시스템kid 헤더 활용, 점진적 키 순환

주제와 관련하여 주목할 내용

카테고리주제항목설명
표준RFC 문서JWT (RFC 7519)JSON Web Token 기본 표준
표준RFC 문서JWS (RFC 7515)JSON Web Signature 서명 표준
표준RFC 문서JWE (RFC 7516)JSON Web Encryption 암호화 표준
표준RFC 문서JWA (RFC 7518)JSON Web Algorithms 알고리즘 표준
표준RFC 문서JWK (RFC 7517)JSON Web Key 키 표준
보안취약점Algorithm Confusionnone 알고리즘 악용 공격
보안취약점Key Confusion공개키/대칭키 혼동 공격
보안취약점Signature Stripping서명 제거 공격
프로토콜통합OAuth 2.0인가 프레임워크와의 결합
프로토콜통합OpenID Connect인증 레이어 구현
라이브러리PythonPyJWT파이썬 JWT 구현체
라이브러리JavaScriptjsonwebtokenNode.js JWT 라이브러리
라이브러리Javajjwt자바 JWT 라이브러리
플랫폼클라우드Auth0관리형 인증 서비스
플랫폼클라우드AWS CognitoAWS 인증 서비스

반드시 학습해야할 내용

카테고리주제항목설명
암호학해시HMAC키 기반 메시지 인증 코드
암호학서명RSA공개키 암호화 알고리즘
암호학서명ECDSA타원곡선 디지털 서명
보안공격XSS크로스 사이트 스크립팅 방어
보안공격CSRF크로스 사이트 요청 위조 방어
아키텍처패턴상태 비저장Stateless 아키텍처 설계
아키텍처패턴마이크로서비스분산 시스템 인증 설계
표준프로토콜OAuth 2.0권한 부여 프레임워크
표준프로토콜OpenID Connect인증 레이어 표준
운영모니터링로깅보안 이벤트 로깅 전략
운영모니터링메트릭토큰 사용량 및 성능 지표

용어 정리

카테고리용어설명
기본 개념JSON Web Token (JWT)JSON 객체로 정보를 안전하게 전송하기 위한 표준 (RFC 7519)
기본 개념ClaimsJWT 페이로드에 포함된 정보 조각들
기본 개념Base64URLURL-safe 한 Base64 인코딩 방식
구조HeaderJWT 의 첫 번째 부분으로 메타데이터 포함
구조PayloadJWT 의 두 번째 부분으로 클레임 정보 포함
구조SignatureJWT 의 세 번째 부분으로 무결성 보장
알고리즘HS256HMAC with SHA-256, 대칭키 알고리즘
알고리즘RS256RSA Signature with SHA-256, 비대칭키 알고리즘
알고리즘ES256ECDSA with P-256 and SHA-256
클레임iss (Issuer)토큰 발급자
클레임sub (Subject)토큰 주체 (사용자 ID)
클레임aud (Audience)토큰 대상
클레임exp (Expiration Time)토큰 만료 시간
클레임nbf (Not Before)토큰 유효 시작 시간
클레임iat (Issued At)토큰 발급 시간
클레임jti (JWT ID)토큰 고유 식별자
보안JWS (JSON Web Signature)디지털 서명을 통한 무결성 보장
보안JWE (JSON Web Encryption)암호화를 통한 기밀성 보장
보안JOSE (JSON Object Signing and Encryption)JSON 기반 암호화 표준 집합
운영Access Token리소스 접근을 위한 단기 토큰
운영Refresh Token액세스 토큰 갱신을 위한 장기 토큰
운영Token Rotation토큰 순환을 통한 보안 강화
운영Blacklist무효화된 토큰 목록
기술HMAC (Hash-based Message Authentication Code)키 기반 메시지 인증 코드
기술RSARivest-Shamir-Adleman 공개키 암호화
기술ECDSAElliptic Curve Digital Signature Algorithm

참고 및 출처


JSON Web Token(JWT) 은 웹 애플리케이션과 서비스 간에 안전하게 정보를 전송하기 위한 개방형 표준 (RFC 7519) 이다. 이 기술은 특히 사용자 인증과 정보 교환을 위한 효율적이고 안전한 방법으로, 분산 시스템과 마이크로서비스 아키텍처에서 널리 사용되고 있다. 오늘날 많은 기업들이 전통적인 세션 기반 인증에서 JWT 기반 인증으로 전환하는 추세를 보이고 있다.

JWT 의 기본 개념

JWT 는 JSON 객체를 암호화하고 서명하여 생성된 문자열로, 세 부분으로 구성된다:
헤더(Header), 페이로드(Payload), 서명(Signature).
각 부분은 점 (.) 으로 구분되며, Base64Url 로 인코딩된다.

1
xxxxx.yyyyy.zzzzz

여기서:

이 구조는 정보를 안전하게 전송하면서도 쉽게 처리할 수 있도록 설계되었다.

JWT 의 세 가지 구성 요소 상세 분석

헤더 (Header)

헤더는 일반적으로 두 가지 정보를 담고 있다:

예시:

1
2
3
4
{
  "alg": "HS256",
  "typ": "JWT"
}

이 JSON 객체는 Base64Url 로 인코딩되어 JWT 의 첫 번째 부분을 형성한다.

페이로드 (Payload)

페이로드에는 사용자 정보나 추가 데이터를 포함하는 클레임 (claim) 이 들어있다.

클레임은 세 가지 유형으로 분류된다:

  1. 등록된 클레임 (Registered claims): 미리 정의된 권장 클레임들
    • iss(발행자)
    • sub(주제)
    • exp(만료 시간)
    • iat(발행 시간)
    • nbf(활성화 시간)
    • jti(JWT ID)
  2. 공개 클레임 (Public claims): JWT 사용자가 정의하지만 충돌을 피하기 위해 IANA JSON Web Token Registry 에 등록하거나 URI 형식으로 정의
  3. 비공개 클레임 (Private claims): 특정 당사자 간에 정보를 공유하기 위해 생성된 사용자 정의 클레임

예시:

1
2
3
4
5
6
7
{
  "sub": "1234567890",
  "name": "홍길동",
  "admin": true,
  "iat": 1625240000,
  "exp": 1625243600
}

이 페이로드도 Base64Url 로 인코딩되어 JWT 의 두 번째 부분을 형성한다. 중요한 점은 이 인코딩이 암호화가 아니라는 것이다. 따라서 페이로드에 민감한 정보 (비밀번호 등) 를 포함해서는 안 된다.

서명 (Signature)

서명은 JWT 의 마지막 부분으로, 인코딩된 헤더, 인코딩된 페이로드, 비밀 키, 그리고 헤더에 명시된 알고리즘을 사용하여 생성된다.

예를 들어, HMAC SHA256 알고리즘을 사용한다면:

1
2
3
4
5
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

이 서명은 메시지가 변경되지 않았는지 확인하고, JWT 를 발행한 당사자가 맞는지 검증하는 데 사용된다.

JWT 작동 메커니즘

JWT 인증의 일반적인 흐름은 다음과 같다:

  1. 사용자 로그인: 사용자가 자격 증명 (사용자 이름/비밀번호) 으로 서버에 로그인한다.

  2. JWT 발급: 서버는 자격 증명을 검증한 후, 비밀 키를 사용하여 JWT 를 생성하고 클라이언트에 반환한다.

  3. JWT 저장: 클라이언트는 JWT 를 로컬 스토리지, 세션 스토리지, 또는 쿠키에 저장한다.

  4. 요청 인증: 클라이언트는 보호된 경로나 리소스에 접근할 때마다 Authorization 헤더에 JWT 를 포함시킨다:

    1
    
    Authorization: Bearer <token>
    
  5. 토큰 검증: 서버는 JWT 의 서명을 검증하고, 만료 여부를 확인한 후 요청을 처리한다.

  6. 응답 반환: 토큰이 유효하면 서버는 요청된 데이터를 응답으로 반환한다.

이러한 프로세스는 서버가 사용자의 상태를 저장할 필요 없이 인증을 처리할 수 있게 해준다 (무상태 인증).

JWT 구현 예시

서버 측 구현 (Node.js/Express)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
const express = require('express');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');

const app = express();
const SECRET_KEY = 'your_secret_key'; // 실제 환경에서는 환경변수 등으로 안전하게 관리해야 함

app.use(bodyParser.json());

// 로그인 엔드포인트
app.post('/login', (req, res) => {
  // 실제 구현에서는 데이터베이스에서 사용자 조회 및 비밀번호 검증 필요
  const { username, password } = req.body;
  
  // 사용자 검증 예시
  if (username === 'admin' && password === 'password') {
    // 사용자 정보 기반으로 JWT 생성
    const user = { id: 1, username: 'admin', role: 'admin' };
    const token = jwt.sign(
      { user_id: user.id, username: user.username, role: user.role },
      SECRET_KEY,
      { expiresIn: '2h' } // 토큰 만료 시간 설정
    );
    
    res.json({ success: true, token });
  } else {
    res.status(401).json({ success: false, message: '인증 실패' });
  }
});

// JWT 검증 미들웨어
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN 형식에서 TOKEN 추출
  
  if (!token) {
    return res.status(401).json({ message: '인증 토큰이 필요합니다' });
  }
  
  jwt.verify(token, SECRET_KEY, (err, decoded) => {
    if (err) {
      return res.status(403).json({ message: '토큰이 유효하지 않습니다' });
    }
    
    req.user = decoded;
    next();
  });
}

// 보호된 라우트
app.get('/protected', authenticateToken, (req, res) => {
  res.json({ 
    message: '인증된 접근 성공', 
    user: req.user 
  });
});

app.listen(3000, () => {
  console.log('서버가 3000번 포트에서 실행 중입니다');
});
클라이언트 측 구현 (JavaScript)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 로그인 함수
async function login(username, password) {
  try {
    const response = await fetch('/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ username, password })
    });
    
    const data = await response.json();
    
    if (!response.ok) {
      throw new Error(data.message || '로그인에 실패했습니다');
    }
    
    // 토큰 저장 (로컬 스토리지 사용 예시)
    localStorage.setItem('token', data.token);
    
    return data;
  } catch (error) {
    console.error('로그인 오류:', error);
    throw error;
  }
}

// 보호된 리소스 요청 함수
async function fetchProtectedResource() {
  try {
    const token = localStorage.getItem('token');
    
    if (!token) {
      throw new Error('인증 토큰이 없습니다. 로그인이 필요합니다.');
    }
    
    const response = await fetch('/protected', {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    
    if (!response.ok) {
      if (response.status === 401 || response.status === 403) {
        // 토큰이 만료되었거나 유효하지 않은 경우
        localStorage.removeItem('token');
        // 로그인 페이지로 리디렉션
        window.location.href = '/login-page';
        return;
      }
      throw new Error('요청에 실패했습니다');
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('보호된 리소스 요청 오류:', error);
    throw error;
  }
}

// 로그아웃 함수
function logout() {
  localStorage.removeItem('token');
  // 로그인 페이지로 리디렉션
  window.location.href = '/login-page';
}

JWT 의 장점

  1. 무상태 (Stateless): 서버는 인증 상태를 저장할 필요가 없어, 수평적 확장이 용이하고 메모리 부담이 적다.
  2. 확장성: 분산 시스템이나 마이크로서비스 아키텍처에서 효과적으로 사용할 수 있다.
  3. 크로스 도메인/CORS: 다른 도메인에서도 토큰 사용이 가능하여 여러 서비스 간 인증 공유가 용이하다.
  4. 표준화: 개방형 표준 (RFC 7519) 을 준수하며, 다양한 언어와 플랫폼에서 구현체가 존재한다.
  5. 자체 포함 (Self-contained): 토큰 자체에 필요한 모든 정보가 포함되어 있어, 추가 조회가 필요 없다.
  6. 디커플링 (Decoupling): 인증 로직과 비즈니스 로직의 분리가 용이하다.
  7. 모바일 호환성: 모바일 환경에서도 쉽게 사용할 수 있다.

JWT 의 단점과 보안 고려사항

  1. 토큰 크기: JWTs 는 세션 ID 에 비해 상대적으로 크기가 클 수 있어, 네트워크 오버헤드가 발생할 수 있다.
  2. 저장된 토큰 취약점: 클라이언트에 저장된 토큰은 XSS 공격에 취약할 수 있다.
  3. 토큰 취소 어려움: 일단 발급된 토큰은 만료되기 전까지 유효하므로, 즉시 무효화하기 어렵다.
  4. 비밀 키 관리: 비밀 키가 노출되면 토큰을 위조할 수 있어, 키 관리가 중요하다.
  5. 페이로드 데이터 제한: 페이로드는 암호화되지 않고 단지 인코딩될 뿐이므로, 민감한 정보를 포함해서는 안 된다.
  6. 만료 시간 관리: 토큰의 만료 시간을 적절하게 설정해야 보안과 사용자 경험 사이의 균형을 맞출 수 있다.

JWT 보안 강화 방법

토큰 저장 보안
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 쿠키에 토큰 저장 (더 안전한 접근 방식)
function setTokenCookie(token) {
  document.cookie = `token=${token}; path=/; secure; samesite=strict; httponly`;
}

// 토큰 쿠키 읽기 함수
function getTokenFromCookie() {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; token=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
  return null;
}
리프레시 토큰 구현
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 서버 측 리프레시 토큰 처리
app.post('/refresh-token', (req, res) => {
  const { refreshToken } = req.body;
  
  // 리프레시 토큰 검증 (실제로는 데이터베이스 조회 필요)
  if (!isValidRefreshToken(refreshToken)) {
    return res.status(401).json({ message: '유효하지 않은 리프레시 토큰' });
  }
  
  // 새 액세스 토큰 발급
  const user = getUserByRefreshToken(refreshToken);
  const newAccessToken = jwt.sign(
    { user_id: user.id, username: user.username, role: user.role },
    SECRET_KEY,
    { expiresIn: '15m' }
  );
  
  res.json({ accessToken: newAccessToken });
});

// 클라이언트 측 토큰 갱신 처리
async function refreshToken() {
  const refreshToken = localStorage.getItem('refreshToken');
  
  if (!refreshToken) {
    // 리프레시 토큰이 없으면 로그인 페이지로 리디렉션
    window.location.href = '/login-page';
    return;
  }
  
  try {
    const response = await fetch('/refresh-token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ refreshToken })
    });
    
    if (!response.ok) {
      throw new Error('토큰 갱신 실패');
    }
    
    const data = await response.json();
    localStorage.setItem('token', data.accessToken);
    
    return data.accessToken;
  } catch (error) {
    // 리프레시 토큰도 만료된 경우, 로그아웃 처리
    logout();
    throw error;
  }
}
토큰 블랙리스트
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Redis를 사용한 토큰 블랙리스트 구현 예시 (Node.js)
const redis = require('redis');
const client = redis.createClient();

// 토큰 블랙리스트에 추가
function addToBlacklist(token, expiryTime) {
  // JWT에서 만료 시간 추출
  const decodedToken = jwt.decode(token);
  const expiry = decodedToken.exp;
  const now = Math.floor(Date.now() / 1000);
  const ttl = expiry - now;
  
  // 만료 시간까지만 블랙리스트에 저장
  if (ttl > 0) {
    client.setex(token, ttl, 'blacklisted');
  }
}

// 토큰이 블랙리스트에 있는지 확인
function isBlacklisted(token) {
  return new Promise((resolve, reject) => {
    client.get(token, (err, reply) => {
      if (err) return reject(err);
      resolve(reply === 'blacklisted');
    });
  });
}

// 로그아웃 엔드포인트
app.post('/logout', authenticateToken, (req, res) => {
  const token = req.headers.authorization.split(' ')[1];
  
  // 토큰 블랙리스트에 추가
  addToBlacklist(token);
  
  res.json({ message: '로그아웃 성공' });
});

// 블랙리스트 확인을 포함한 인증 미들웨어
async function authenticateTokenWithBlacklist(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ message: '인증 토큰이 필요합니다' });
  }
  
  // 블랙리스트 확인
  try {
    const blacklisted = await isBlacklisted(token);
    if (blacklisted) {
      return res.status(401).json({ message: '세션이 만료되었습니다. 다시 로그인해주세요.' });
    }
    
    jwt.verify(token, SECRET_KEY, (err, decoded) => {
      if (err) {
        return res.status(403).json({ message: '토큰이 유효하지 않습니다' });
      }
      
      req.user = decoded;
      next();
    });
  } catch (error) {
    return res.status(500).json({ message: '인증 처리 중 오류가 발생했습니다' });
  }
}

실제 적용 시나리오

  1. 단일 페이지 애플리케이션 (SPA)
    React, Vue, Angular 등의 프론트엔드 프레임워크로 구축된 SPA 에서는 JWT 를 사용하여 백엔드 API 와 통신하는 것이 일반적이다. 사용자가 로그인하면 JWT 를 발급받아 로컬 스토리지나 쿠키에 저장하고, 이후 API 요청에 사용한다.

  2. 마이크로서비스 아키텍처
    여러 마이크로서비스로 구성된 시스템에서는 JWT 가 서비스 간 인증을 위한 효율적인 방법을 제공한다. 사용자가 하나의 서비스에 인증하면, 발급된 JWT 를 다른 서비스에도 사용할 수 있어 통합 인증이 가능하다.

  3. 모바일 애플리케이션
    모바일 앱과 백엔드 서버 간의 통신에서도 JWT 는 효과적인 인증 방식이다. 토큰 기반 접근 방식은 세션 관리의 복잡성을 줄이고, 오프라인 상태에서도 일부 기능을 사용할 수 있게 한다.

JWT 구현 라이브러리 및 도구

주요 언어별 JWT 라이브러리
JWT 디버깅 및 검증 도구

고급 JWT 기법

JWE(JSON Web Encryption)

JWT 의 확장 형태로, 페이로드를 암호화하여 내용 자체를 보호한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Node.js에서 JWE 구현 예시 (jose 라이브러리 사용)
const jose = require('jose');

async function encryptJWT(payload, recipientPublicKey) {
  // JWE 형식으로 암호화
  const jwe = await new jose.CompactEncrypt(
    new TextEncoder().encode(JSON.stringify(payload))
  )
    .setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' })
    .encrypt(recipientPublicKey);
  
  return jwe;
}

async function decryptJWT(jwe, privateKey) {
  // JWE 복호화
  const { plaintext } = await jose.compactDecrypt(jwe, privateKey);
  return JSON.parse(new TextDecoder().decode(plaintext));
}
JWK(JSON Web Key)

JWT 서명 및 암호화에 사용되는 키를 JSON 형식으로 표현하는 방법.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// JWK 세트 생성 예시
const keyPair = await jose.generateKeyPair('RS256');
const publicJwk = await jose.exportJWK(keyPair.publicKey);

// JWK 세트 엔드포인트
app.get('/.well-known/jwks.json', (req, res) => {
  res.json({
    keys: [
      {
        kty: publicJwk.kty,
        kid: '12345',
        use: 'sig',
        alg: 'RS256',
        n: publicJwk.n,
        e: publicJwk.e
      }
    ]
  });
});

JWT 의 미래 전망과 발전 방향

  1. 더 강력한 암호화: 양자 컴퓨팅의 발전에 대비한 새로운 암호화 알고리즘의 도입
  2. 표준화된 취소 메커니즘: JWT 취소를 위한 표준 방법 개발
  3. 경량화된 프로필: IoT 및 제한된 리소스 환경을 위한 경량 JWT 형식
  4. 자동화된 보안 검증: JWT 구현의 보안 취약점을 자동으로 감지하는 도구
  5. 페더레이션 표준과의 통합: OpenID Connect, SAML 과 같은 다른 인증 표준과의 더 나은 통합

참고 및 출처

Secure JWT Storage: Best Practices