Keycloak 과 기업 인증 · OIDC 토큰과 검증 · 이론
JWT 를 서버에 묻지 않고 검증하기
한 줄 요약
JWT 검증은 인증 서버에 물어보는 것이 아니라, 공개키로 서명을 확인하고 클레임을 검사하는 로컬 연산이다. 그래서 빠르다.
왜 이게 필요했나
토큰이 유효한지 확인하는 방법은 두 가지입니다. 인증 서버의 introspection 엔드포인트에 물어보거나, 토큰 자체의 서명을 검증하는 것입니다.
전자는 정확합니다. 방금 취소된 토큰도 즉시 무효로 나옵니다. 대신 매 요청마다 네트워크 왕복이 생깁니다. 마이크로서비스 열 개가 각각 물어보면 인증 서버가 병목이 됩니다.
후자는 네트워크 없이 끝납니다. 공개키만 있으면 되고, 공개키는 캐시할 수 있습니다. 대신 취소를 즉시 반영하지 못합니다. 토큰이 만료될 때까지는 유효한 것으로 보입니다.
실무의 답은 정해져 있습니다. 액세스 토큰을 짧게(5~15분) 두고 로컬 검증을 씁니다. 취소 반영 지연을 그 수명만큼으로 제한하는 것입니다. 리프레시 토큰 회전을 함께 쓰면 블랙리스트 없이도 실질적 무효화 효과를 얻습니다.
어떻게 동작하나
JWT 는 점으로 구분된 세 부분입니다. 헤더, 페이로드, 서명. 앞의 둘은 Base64URL 인코딩일 뿐 암호화가 아닙니다. 누구나 읽을 수 있습니다. 그래서 JWT 에 비밀을 담으면 안 됩니다.
검증 순서는 이렇습니다.
먼저 헤더의 alg 와 kid 를 봅니다. alg 가 none 이거나 예상과 다르면 즉시 거부합니다. 이것을 확인하지 않아 서명 우회가 일어난 사고가 실제로 있습니다.
kid 로 JWKS 엔드포인트에서 해당 공개키를 찾습니다. JWKS 는 캐시하되 kid 를 못 찾으면 한 번 갱신합니다 — 키 회전 때 필요합니다.
서명을 검증합니다. RS256 이면 공개키로 RSA 검증입니다.
그다음 클레임을 봅니다. exp(만료), iss(발급자가 우리가 아는 그 서버인가), aud(이 토큰이 우리 API 를 위한 것인가), 그리고 필요하면 nbf 와 azp 입니다.
aud 검사를 빠뜨리는 실수가 특히 흔합니다. 같은 인증 서버가 발급한 다른 API 용 토큰이 우리 API 에서 통과하게 됩니다.
ID 토큰과 액세스 토큰을 혼동하는 것도 흔합니다. ID 토큰은 클라이언트 앱이 "사용자가 누구인지" 확인하기 위한 것이고 aud 가 클라이언트입니다. API 호출에는 액세스 토큰을 써야 합니다.
현장에서 만나는 모습
키 회전이 있으면 JWKS 에 여러 키가 동시에 존재합니다. kid 로 고르는 이유입니다. 캐시 TTL 을 너무 길게 잡으면 회전 직후 모든 검증이 실패하고, 너무 짧으면 인증 서버로 요청이 몰립니다. 보통 몇 시간 캐시하되 kid 미스 시 즉시 갱신하는 방식을 씁니다.
다음 실습에서 할 것
Keycloak 에서 토큰을 받아 세 부분으로 뜯어보고, 클레임을 확인하고, JWKS 에서 키를 찾아 서명을 검증하고, 페이로드를 변조해 검증이 실패하는 것까지 확인합니다. 마지막에 introspection 과 로컬 검증의 소요 시간을 100회씩 비교합니다.