Skip to content

필사 모드: JWT 보안 완전 정복 — 서명 검증, 키 회전, 흔한 취약점과 방어

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

JWT(JSON Web Token)는 현대 인증 인프라의 혈액과 같습니다. OIDC의 ID 토큰, OAuth의 access token, 마이크로서비스 간 인증, AI 에이전트의 위임 증명까지 — 거의 모든 토큰이 JWT 형식으로 흘러 다닙니다. Keycloak 26.6이 JWT Authorization Grant를 지원하기 시작한 것에서 보듯, JWT의 활용 범위는 2026년에도 계속 넓어지고 있습니다.

문제는 JWT가 "서명된 JSON일 뿐"이라는 단순함 때문에 검증을 대충 구현한 시스템이 너무 많다는 점입니다. alg를 none으로 바꿔치기해서 서명 검증을 통과하고, RS256 공개키를 HMAC 비밀키로 둔갑시켜 토큰을 위조하고, kid 헤더에 SQL 인젝션을 시도하는 공격은 CTF 문제가 아니라 실제 CVE 목록에 있는 사건들입니다.

이 글에서는 JWT/JWS/JWE의 구조부터 시작해, 알려진 공격 기법과 방어법, JWKS 기반 키 회전 전략, 언어별 안전한 검증 코드, 그리고 영원한 숙제인 revocation 문제까지 실무에 필요한 거의 모든 것을 다룹니다.

JWT, JWS, JWE — 용어부터 정리

세 용어는 자주 혼용되지만 계층이 다릅니다.

| 스펙 | RFC | 역할 |

| --- | --- | --- |

| JWT | RFC 7519 | 클레임(주장)을 담는 토큰 형식의 추상 정의 |

| JWS | RFC 7515 | 서명으로 무결성을 보장하는 구체적 직렬화 (대부분의 JWT) |

| JWE | RFC 7516 | 암호화로 기밀성까지 보장하는 직렬화 |

| JWK / JWKS | RFC 7517 | 키와 키 집합의 JSON 표현 |

| JWA | RFC 7518 | 사용 가능한 알고리즘 목록 |

흔히 말하는 JWT는 99%가 JWS Compact Serialization입니다. 중요한 차이를 하나만 기억해야 한다면 이것입니다.

> **JWS는 서명만 한다. 페이로드는 누구나 읽을 수 있다.** Base64URL은 암호화가 아닙니다. 토큰에 개인정보나 비밀을 넣으면 안 되는 이유입니다. 기밀성이 필요하면 JWE를 쓰거나, 애초에 토큰에 담지 말아야 합니다.

JWS 해부 — 헤더, 페이로드, 서명

JWS Compact Serialization은 점(.)으로 구분된 세 부분입니다.

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIwMjYtMDYta2V5In0

.

eyJpc3MiOiJodHRwczovL2lkcC5leGFtcGxlLmNvbS9yZWFsbXMvbXlyZWFsbSIsInN1YiI6ImFsaWNlIiwiYXVkIjoib3JkZXItYXBpIiwiZXhwIjoxNzgwMDAwOTAwLCJpYXQiOjE3ODAwMDAwMDB9

.

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

└── BASE64URL(header) . BASE64URL(payload) . BASE64URL(signature)

디코딩하면 헤더와 페이로드는 이런 JSON입니다.

{

"alg": "RS256",

"typ": "JWT",

"kid": "2026-06-key"

}

{

"iss": "https://idp.example.com/realms/myrealm",

"sub": "alice",

"aud": "order-api",

"exp": 1780000900,

"iat": 1780000000,

"scope": "openid profile orders:read"

}

서명은 다음과 같이 계산됩니다.

signing_input = BASE64URL(header) + "." + BASE64URL(payload)

RS256: signature = RSASSA-PKCS1-v1_5-SHA256( private_key, signing_input )

ES256: signature = ECDSA-P256-SHA256( private_key, signing_input )

EdDSA: signature = Ed25519( private_key, signing_input )

HS256: signature = HMAC-SHA256( shared_secret, signing_input )

핵심 통찰: **헤더는 공격자가 마음대로 조작할 수 있는 입력값**입니다. alg, kid, jku, x5u 같은 헤더 파라미터를 "신뢰할 수 있는 메타데이터"로 취급하는 순간 취약점이 생깁니다. 검증 로직이 헤더의 지시를 따르는 것이 아니라, **서버가 미리 정한 정책이 헤더를 검열**해야 합니다.

공격 기법 1 — alg 혼동 공격

none 알고리즘 공격

JWA 스펙에는 서명 없는 "Unsecured JWT"를 위한 alg none이 정의되어 있습니다. 초기 라이브러리들은 토큰 헤더의 alg를 그대로 믿었기 때문에, 공격자가 헤더를 다음과 같이 바꾸면:

{

"alg": "none",

"typ": "JWT"

}

서명 부분을 비워버린 토큰이 "검증 통과" 처리되는 사고가 있었습니다(2015년 다수 라이브러리 CVE). 페이로드를 마음대로 위조할 수 있으니 치명적입니다.

위조 토큰: BASE64URL(none헤더) . BASE64URL(위조페이로드) .

└ 서명 없음

RS256 → HS256 키 혼동 공격

더 교묘한 변종입니다. 서버가 RS256(비대칭) 검증을 하면서 공개키를 들고 있을 때, 공격자가 alg를 HS256(대칭)으로 바꾸면 어떻게 될까요?

1. 서버의 RSA 공개키는 공개되어 있음 (JWKS 엔드포인트 등)

2. 공격자: alg를 HS256으로 변경한 위조 토큰 생성

3. 공격자: HMAC 비밀키로 "RSA 공개키의 PEM 문자열"을 사용해 서명

4. 취약한 서버: alg=HS256을 보고 HMAC 검증 수행

이때 검증 키로 RSA 공개키(서버가 가진 그 키)를 사용

5. HMAC(공개키PEM, 페이로드) 가 일치 → 위조 토큰 통과

공개키는 비밀이 아니므로 공격자도 같은 키로 HMAC 서명을 만들 수 있다는 점을 악용한 것입니다.

방어 — 알고리즘 허용 목록 고정

방어 원칙은 단 하나입니다. **검증 시 허용할 알고리즘을 서버 코드에 하드코딩하고, 토큰 헤더의 alg는 그 목록과 대조만 한다.**

나쁜 코드: verify(token, key) ← alg를 토큰이 결정

좋은 코드: verify(token, key, algorithms=[RS256]) ← alg를 서버가 결정

최신 라이브러리는 대부분 알고리즘 명시를 강제하지만, 레거시 코드와 직접 구현한 검증 로직은 반드시 점검해야 합니다. 또한 키 타입과 알고리즘 패밀리를 묶어서 관리하면(RSA 키는 RS/PS 계열만, EC 키는 ES 계열만) 키 혼동 자체가 불가능해집니다.

공격 기법 2 — kid 인젝션

kid(Key ID) 헤더는 "어떤 키로 검증할지"를 알려주는 힌트입니다. 서버가 kid 값을 검증 없이 사용하면 인젝션 통로가 됩니다.

시나리오 A: 경로 조작 (path traversal)

헤더: "kid": "../../../../dev/null"

서버가 kid로 파일 시스템에서 키를 읽는 구조라면

/dev/null(빈 내용)이 키가 됨 → 빈 키로 서명한 위조 토큰 통과

시나리오 B: SQL 인젝션

헤더: "kid": "x' UNION SELECT 'attacker-known-secret' --"

서버가 SELECT key FROM keys WHERE kid = '...' 식으로 조회하면

공격자가 아는 값이 검증 키로 반환됨

시나리오 C: jku/x5u 헤더 악용

헤더: "jku": "https://evil.com/jwks.json"

서버가 jku URL에서 키를 가져오면 공격자 키로 검증하게 됨

방어법은 다음과 같습니다.

1. kid는 **불투명한 식별자**로만 취급하고, 사전에 로드된 키 맵에서 정확히 일치하는 항목을 조회합니다. 일치 항목이 없으면 즉시 거부합니다.

2. kid 값에 형식 제약을 둡니다(영숫자와 하이픈만 허용 등).

3. jku, x5u 헤더는 무시하거나, 허용 목록의 URL(자사 IdP의 JWKS)만 사용합니다.

4. 키 조회 경로에 파일 시스템/DB 동적 조회를 넣지 않습니다.

클레임 검증 — 서명만 보면 절반만 검증한 것

서명이 유효하다는 것은 "IdP가 발급했다"는 의미일 뿐, "이 API를 위해, 지금, 이 용도로 발급했다"는 의미가 아닙니다. 표준 클레임을 모두 검증해야 합니다.

| 클레임 | 의미 | 검증 규칙 |

| --- | --- | --- |

| iss | 발급자 | 기대하는 issuer URL과 정확히 일치 |

| aud | 대상 | 내 서비스 식별자가 포함되어 있는지 확인 |

| exp | 만료 시각 | 현재 시각이 exp 이전인지 (clock skew 60~120초 허용) |

| nbf | 유효 시작 | 현재 시각이 nbf 이후인지 |

| iat | 발급 시각 | 비정상적으로 미래/과거인 토큰 거부 |

| sub | 주체 | 비어 있지 않은지, 내부 사용자 모델과 매핑되는지 |

| typ / token_use | 토큰 종류 | access token과 ID 토큰 혼용 차단 |

특히 **aud 검증 누락**은 가장 흔하면서 위험한 실수입니다. 같은 IdP가 발급한 토큰이라도 "결제 API용 토큰"으로 "관리자 API"를 호출할 수 있게 되기 때문입니다. 마이크로서비스 환경에서는 서비스마다 고유한 audience를 부여하고, 각자 자기 audience만 수락하도록 해야 토큰의 가로 이동(lateral movement)을 막을 수 있습니다.

ID 토큰과 access token의 혼용도 주의해야 합니다. ID 토큰은 "클라이언트에게 사용자가 누구인지 알려주는" 용도이고, API 호출 인가에 쓰면 안 됩니다. RFC 9068(JWT access token 프로파일)은 헤더에 typ을 at+jwt로 명시해 이 혼용을 구조적으로 차단합니다.

대칭 vs 비대칭 — 그리고 EdDSA

알고리즘 비교

| 알고리즘 | 종류 | 키 | 특징 |

| --- | --- | --- | --- |

| HS256 | 대칭 HMAC | 공유 비밀키 | 빠름. 검증자도 발급 가능해짐(부인 방지 불가) |

| RS256 | 비대칭 RSA | 2048비트 이상 | 가장 널리 호환. 서명 크기 큼(256바이트) |

| PS256 | 비대칭 RSA-PSS | 2048비트 이상 | RS256의 개선판(확률적 패딩) |

| ES256 | 비대칭 ECDSA P-256 | 256비트 | 서명 64바이트. 논스 재사용 시 키 유출 위험 |

| EdDSA (Ed25519) | 비대칭 | 256비트 | 결정적 서명, 빠르고 부채널 내성. 2026년 권장 |

선택 기준은 간단합니다.

- **단일 서비스가 자기 토큰을 발급/검증**: HS256도 가능하지만, 비밀키가 모든 검증자에게 퍼지는 순간 발급 권한도 퍼집니다. 구조가 커질 것 같으면 처음부터 비대칭으로 가세요.

- **IdP가 발급하고 여러 서비스가 검증**: 무조건 비대칭. 검증자는 공개키만 가지므로 유출돼도 위조가 불가능합니다.

- **새 시스템의 알고리즘**: EdDSA(Ed25519)가 제1후보입니다. ECDSA의 논스 재사용 문제가 없는 결정적 서명이고, 서명/검증 속도가 빠르며 서명 크기도 작습니다. **Keycloak 26.6부터 EdDSA를 정식 지원**하므로 realm 키 프로바이더에서 Ed25519를 선택할 수 있습니다. 단, 오래된 클라이언트 라이브러리의 호환성은 사전 확인이 필요합니다.

토큰 크기와 성능

JWT는 모든 요청 헤더에 실려 다니므로 크기가 곧 비용입니다.

대략적인 크기 비교 (동일 클레임 기준)

HS256: 헤더+페이로드 + 32바이트 서명 → 가장 작음

ES256: 헤더+페이로드 + 64바이트 서명

EdDSA: 헤더+페이로드 + 64바이트 서명

RS256: 헤더+페이로드 + 256바이트 서명 → Base64 후 약 342자 증가

주의: 페이로드 비대화가 더 큰 문제

- 그룹/권한 목록을 통째로 넣은 토큰이 8KB를 넘어

웹서버 헤더 한도(기본 8KB)를 초과하는 사고가 흔함

- 권한은 토큰에 "참조"만 넣고 상세는 API로 조회하는 설계 권장

검증 성능은 EdDSA와 ECDSA 검증이 RSA 검증보다 빠르거나 비슷하고, RSA는 서명 생성이 특히 느립니다. 토큰 발급량이 많은 IdP일수록 EdDSA의 이점이 큽니다.

JWKS 기반 키 회전 전략

서명 키는 영원히 쓰면 안 됩니다. 키가 오래될수록 유출 가능성이 누적되고, 유출 시 피해 범위가 커집니다. 표준 패턴은 JWKS(JSON Web Key Set) 엔드포인트와 kid를 이용한 무중단 회전입니다.

JWKS 엔드포인트 (Keycloak 예시)

GET https://idp.example.com/realms/myrealm/protocol/openid-connect/certs

{

"keys": [

{

"kid": "2026-06-key",

"kty": "OKP",

"crv": "Ed25519",

"alg": "EdDSA",

"use": "sig",

"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"

},

{

"kid": "2026-03-key",

"kty": "RSA",

"alg": "RS256",

"use": "sig",

"n": "0vx7agoebGcQSuuPiLJXZpt...",

"e": "AQAB"

}

]

}

무중단 회전 절차는 다음과 같습니다.

단계 1: 새 키 생성, JWKS에 추가 (서명은 아직 구 키로)

→ 모든 검증자가 새 키를 캐시할 시간을 확보

단계 2: 서명 키를 새 키로 전환 (구 키는 JWKS에 유지)

→ 구 키로 서명된 미만료 토큰도 계속 검증됨

단계 3: 구 키로 서명된 토큰이 모두 만료된 후 구 키를 JWKS에서 제거

타이밍 규칙:

단계1 → 단계2 간격 >= 검증자의 JWKS 캐시 TTL

단계2 → 단계3 간격 >= access token 최대 수명

Keycloak은 이 패턴을 키 프로바이더의 priority와 active/passive 상태로 구현합니다. 새 키를 높은 priority로 추가하면 서명이 전환되고, 구 키는 passive로 남겨 검증만 담당시킨 뒤 나중에 제거합니다.

검증자 쪽 베스트 프랙티스도 중요합니다.

1. JWKS를 매 요청마다 가져오지 말고 캐시합니다(5~15분 TTL).

2. **모르는 kid를 만나면 한 번만 JWKS를 강제 갱신**해 봅니다. 회전 직후의 자연스러운 상황입니다. 그래도 없으면 거부합니다.

3. JWKS 갱신 실패 시 기존 캐시로 동작을 유지합니다(가용성).

4. JWKS 엔드포인트 호출에 rate limit을 두어, 위조 kid 폭주로 인한 DoS를 막습니다.

안전한 검증 코드 — Java / Node / Go

Java (Spring Security Resource Server)

가장 안전한 방법은 직접 검증하지 않는 것입니다. Spring Security에 위임하세요.

application.yml

spring:

security:

oauth2:

resourceserver:

jwt:

issuer-uri: https://idp.example.com/realms/myrealm

audiences: order-api

// 추가 클레임 검증이 필요할 때 (JwtDecoder 커스터마이즈)

@Bean

JwtDecoder jwtDecoder() {

NimbusJwtDecoder decoder = JwtDecoders.fromIssuerLocation(

"https://idp.example.com/realms/myrealm");

OAuth2TokenValidator<Jwt> withIssuer =

JwtValidators.createDefaultWithIssuer(

"https://idp.example.com/realms/myrealm");

OAuth2TokenValidator<Jwt> withAudience = new JwtClaimValidator<List<String>>(

"aud", aud -> aud != null && aud.contains("order-api"));

decoder.setJwtValidator(

new DelegatingOAuth2TokenValidator<>(withIssuer, withAudience));

return decoder;

}

issuer-uri 방식은 OIDC discovery로 JWKS 위치와 지원 알고리즘을 자동 구성하고, 기본적으로 exp/nbf와 issuer를 검증합니다. audience 검증은 위처럼 명시적으로 추가해야 한다는 점을 잊지 마세요.

Node.js (jose 라이브러리)

const JWKS = createRemoteJWKSet(

new URL('https://idp.example.com/realms/myrealm/protocol/openid-connect/certs'),

{ cooldownDuration: 30000, cacheMaxAge: 600000 }

);

export async function verifyAccessToken(token) {

const { payload } = await jwtVerify(token, JWKS, {

issuer: 'https://idp.example.com/realms/myrealm',

audience: 'order-api',

algorithms: ['EdDSA', 'RS256'], // 허용 알고리즘 하드코딩

clockTolerance: '60s',

});

if (payload.token_use && payload.token_use !== 'access') {

throw new Error('not an access token');

}

return payload;

}

jose는 createRemoteJWKSet이 kid 매칭, 캐싱, 미지 kid 시 1회 재조회까지 처리해 줍니다. algorithms를 반드시 명시하는 습관이 중요합니다.

Go (lestrrat-go/jwx)

package auth

"context"

"errors"

"time"

"github.com/lestrrat-go/jwx/v2/jwk"

"github.com/lestrrat-go/jwx/v2/jwt"

)

var cache *jwk.Cache

func InitJWKS(ctx context.Context) error {

cache = jwk.NewCache(ctx)

return cache.Register(

"https://idp.example.com/realms/myrealm/protocol/openid-connect/certs",

jwk.WithMinRefreshInterval(10*time.Minute),

)

}

func VerifyAccessToken(ctx context.Context, raw string) (jwt.Token, error) {

keySet, err := cache.Get(ctx,

"https://idp.example.com/realms/myrealm/protocol/openid-connect/certs")

if err != nil {

return nil, err

}

tok, err := jwt.Parse([]byte(raw),

jwt.WithKeySet(keySet), // kid 기반 키 선택

jwt.WithIssuer("https://idp.example.com/realms/myrealm"),

jwt.WithAudience("order-api"),

jwt.WithAcceptableSkew(60*time.Second), // clock skew 허용

jwt.WithValidate(true),

)

if err != nil {

return nil, err

}

if tok.Subject() == "" {

return nil, errors.New("missing sub claim")

}

return tok, nil

}

세 언어 모두 공통 원칙은 동일합니다. **(1) 알고리즘 허용 목록 고정, (2) issuer/audience 명시 검증, (3) JWKS 캐싱과 kid 매칭은 라이브러리에 위임, (4) 직접 Base64 디코딩해서 검증 로직을 손으로 짜지 않기.**

저장 위치 논쟁 — localStorage vs Cookie

SPA에서 토큰을 어디에 둘 것인가는 10년째 이어지는 논쟁입니다. 정리하면 다음과 같습니다.

| 저장 위치 | XSS 노출 | CSRF 노출 | 비고 |

| --- | --- | --- | --- |

| localStorage | 취약 (JS로 읽힘) | 안전 | 탈취 시 토큰 자체가 유출 |

| sessionStorage | 취약 | 안전 | 탭 닫으면 소멸, 동일 한계 |

| 메모리 (JS 변수) | 부분 취약 | 안전 | 새로고침 시 소실, 탈취 난도는 높아짐 |

| HttpOnly Cookie | 안전 (읽기 불가) | 취약 → SameSite로 완화 | 토큰 값 자체는 보호됨 |

냉정한 현실 인식이 필요합니다. **XSS가 일어나면 어디에 저장했든 게임 오버에 가깝습니다.** HttpOnly 쿠키여도 공격 스크립트가 사용자의 브라우저에서 직접 API를 호출하면 되기 때문입니다. 다만 토큰 값 자체의 유출(오프라인 악용, 다른 IP에서 사용)을 막는다는 점에서 HttpOnly 쿠키가 우위에 있습니다.

2026년 기준 권장 순서는 다음과 같습니다.

1. **BFF 패턴**: 토큰은 서버에만 존재. 브라우저에는 HttpOnly + Secure + SameSite=Strict 세션 쿠키. SPA 보안의 사실상 종결안입니다.

2. BFF가 어렵다면: access token은 메모리, refresh token은 rotation을 켠 상태로 HttpOnly 쿠키.

3. localStorage에 refresh token을 두는 것은 피하세요. XSS 한 번으로 장기 자격증명이 통째로 유출됩니다.

Revocation 문제 — JWT의 아킬레스건

JWT의 최대 장점인 "상태 비저장 검증"은 최대 약점이기도 합니다. 발급된 토큰은 exp까지 유효하며, 서버가 "이 토큰만 무효"라고 선언할 표준 수단이 없습니다. 해법은 트레이드오프의 스펙트럼입니다.

완전 stateless ◄──────────────────────────────► 완전 stateful

짧은 수명 denylist introspection opaque 토큰

(5~15분) (jti 블랙리스트) (RFC 7662) + 세션 저장소

즉시 차단 불가 무효화만 저장 매 요청 IdP 조회 JWT 포기

실무 권장 조합은 다음과 같습니다.

1. **access token 수명을 5~15분으로 단축** — 탈취 피해 창을 구조적으로 줄입니다. 대부분의 서비스는 이것만으로 충분합니다.

2. **refresh token은 stateful하게 관리** — rotation + reuse detection + 서버 측 무효화. "로그아웃"과 "강제 차단"은 refresh 계층에서 실현됩니다.

3. **고위험 작업에는 추가 검증** — 결제, 권한 변경 등은 introspection(RFC 7662)이나 재인증을 요구합니다.

4. **비상 차단용 denylist** — 사고 대응 시 특정 jti/sub를 차단하는 짧은 TTL 캐시(Redis 등)를 준비해 둡니다. exp가 짧으므로 denylist도 작게 유지됩니다.

5. **이벤트 기반 전파** — 대규모 환경이라면 OpenID Shared Signals/CAEP로 세션 무효화 이벤트를 구독하는 아키텍처도 검토할 만합니다.

안티패턴 모음

마지막으로 현장에서 반복적으로 발견되는 안티패턴을 정리합니다.

| 안티패턴 | 무엇이 문제인가 | 교정 |

| --- | --- | --- |

| 토큰 디코딩만 하고 검증 생략 | Base64 디코딩은 검증이 아님 | 서명+클레임 전체 검증 |

| alg를 토큰 헤더에서 읽어 분기 | alg 혼동 공격의 입구 | 허용 목록 하드코딩 |

| aud 검증 생략 | 토큰 가로 이동 허용 | 서비스별 고유 audience |

| 페이로드에 비밀번호/PII 저장 | JWS는 평문 공개 | JWE 또는 비저장 |

| 만료 없는 토큰 발급 | 영구 자격증명화 | exp 필수, 짧게 |

| HS256 비밀키를 짧은 문자열로 | 무차별 대입에 취약 | 256비트 이상 랜덤 키 |

| 에러 메시지에 검증 실패 사유 상세 노출 | 공격자에게 피드백 제공 | 일반화된 401 응답 |

| 서명 검증을 게이트웨이만 수행 | 내부 직접 호출 시 무검증 | zero trust, 각 서비스가 검증 |

마치며

JWT 보안의 본질은 한 문장으로 요약됩니다. **토큰이 하는 말이 아니라, 서버가 정한 정책을 믿어야 합니다.** alg도, kid도, 클레임도 모두 공격자가 채울 수 있는 입력값이며, 검증 코드는 이 입력값을 정책의 잣대로 검열하는 문지기입니다.

- 구조: JWS는 서명만, 기밀성은 JWE. 페이로드는 공개 정보로 취급하세요.

- 공격: alg none, RS256→HS256, kid 인젝션은 모두 "헤더를 믿어서" 생깁니다. 알고리즘과 키 출처를 서버에 고정하세요.

- 클레임: iss/aud/exp/nbf를 빠짐없이. 특히 aud 검증이 마이크로서비스의 가로 이동을 막습니다.

- 키 관리: JWKS + kid로 무중단 회전을 자동화하고, 새 시스템은 EdDSA를 우선 검토하세요(Keycloak 26.6 정식 지원).

- 수명주기: 짧은 access token + stateful refresh token + 비상용 denylist 조합이 현실적인 균형점입니다.

검증 코드는 한 번 작성하면 수년간 모든 요청을 통과시키는 코드입니다. 이 글의 체크리스트를 들고 지금 운영 중인 서비스의 검증 로직을 한 번 점검해 보시기 바랍니다.

참고 자료

- [RFC 7519 - JSON Web Token (JWT)](https://datatracker.ietf.org/doc/html/rfc7519)

- [RFC 7515 - JSON Web Signature (JWS)](https://datatracker.ietf.org/doc/html/rfc7515)

- [RFC 7516 - JSON Web Encryption (JWE)](https://datatracker.ietf.org/doc/html/rfc7516)

- [RFC 7517 - JSON Web Key (JWK)](https://datatracker.ietf.org/doc/html/rfc7517)

- [RFC 7518 - JSON Web Algorithms (JWA)](https://datatracker.ietf.org/doc/html/rfc7518)

- [RFC 8725 - JSON Web Token Best Current Practices](https://datatracker.ietf.org/doc/html/rfc8725)

- [RFC 9068 - JWT Profile for OAuth 2.0 Access Tokens](https://datatracker.ietf.org/doc/html/rfc9068)

- [RFC 7662 - OAuth 2.0 Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662)

- [RFC 8037 - CFRG Elliptic Curve Signatures in JOSE (Ed25519)](https://datatracker.ietf.org/doc/html/rfc8037)

- [RFC 9700 - Best Current Practice for OAuth 2.0 Security](https://datatracker.ietf.org/doc/html/rfc9700)

- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)

- [Keycloak Documentation](https://www.keycloak.org/documentation)

- [Keycloak 26.6.0 Release Notes](https://www.keycloak.org/2026/04/keycloak-2660-released)

현재 단락 (1/280)

JWT(JSON Web Token)는 현대 인증 인프라의 혈액과 같습니다. OIDC의 ID 토큰, OAuth의 access token, 마이크로서비스 간 인증, AI 에이전트의 위...

작성 글자: 0원문 글자: 11,493작성 단락: 0/280