게이트웨이 JWT 검증 — exp 누락
로그인한 사용자가 API 를 부를 때마다 아이디와 비밀번호를 다시 보낼 수는 없으니, 로그인 한 번에 토큰을 하나 발급해 주고 그다음부터는 요청 헤더에 그 토큰을 붙여 보냅니다. 서버는 토큰만 보고 누가 보낸 요청인지 판단하고, 이때 흔히 쓰는 형식이 JWT 입니다. 사용자 정보와 만료 시각을 담아 서명해 둔 문자열이라, 받는 쪽이 DB 를 뒤지지 않고 서명만 맞춰 봐도 위조가 아니라는 걸 알 수 있습니다.
그 확인을 어디서 하느냐가 갈립니다. 뒤에 붙은 서비스마다 검사 코드를 넣으면 어디는 서명만 보고 어디는 만료까지 보는 식으로 갈라지고 새로 올린 서비스에서는 통째로 빠지기도 합니다. 그래서 API 앞에 인증 게이트웨이를 하나 두고 검증은 거기서만 하기로 했습니다. 뒤 서비스는 게이트웨이를 지나온 요청이면 인증이 끝난 걸로 보고 아무것도 다시 확인하지 않습니다.
그러면 게이트웨이가 무엇을 보느냐가 전부가 됩니다. JWT 안의 exp 는 이 토큰이 언제까지
유효한지를 Unix 타임스탬프로 적어 둔 값이라, 검증하는 쪽이 지금 시각과 비교해 지났으면 거절해야
합니다. 토큰이 새어 나가도 쓸 수 있는 시간이 정해져 있다는 게 이 구조의 전제고, 그 전제를 지키는
자리가 게이트웨이 한 군데뿐입니다.
그런데 만료된 토큰으로 호출해도 그대로 통과했습니다.
만료 토큰이 200 반환
TTL 5초짜리 JWT 를 발급하고 8초 뒤에 재사용해봤습니다.
# JWT TTL 5초로 발급, 8초 후 재사용
curl -H "Authorization: Bearer <expired_jwt>" \
http://<gateway>/api -X POST ...
# → HTTP 200 (예상: 401)
만료된 토큰으로 호출해도 그대로 성공하고 몇 시간 뒤에 같은 토큰으로 다시 던져도 200 이 옵니다. 게이트웨이 로그를 봐도 만료로 거부한 기록이 없습니다.
이게 왜 위험하냐면 탈취된 토큰이 영원히 유효하다는 뜻이기 때문입니다. JWT 를 짧은 수명으로 발급하는 이유가 “새면 금방 만료되게” 라서, 만료를 검증 안 하면 그 설계가 통째로 무의미해집니다.
서명은 검증, exp만 무시
원인을 좁히려고 하나씩 배제했습니다. 헤더의 alg 나 kid 를 바꿔 서명이 안 맞는 토큰을 넣어 보니 제대로 거부했고, 그러니 서명 검증 로직 자체는 살아 있었습니다. 인증 모드를 Strict 로 두든 Permissive
로 두든 exp 는 똑같이 무시했으니 설정으로 켜고 끄는 문제도 아니었습니다.
남는 건 게이트웨이가 서명은 확인하면서 exp 클레임 검증만 빠져 있거나 버그로 동작을 안 하는
상태라는 것이었습니다. 특정 버전의 문제였고 그 버전에서는 무엇을 설정해도 만료 검증이 안
됐습니다.
만료 검증을 게이트웨이 정책에서 직접
버전을 올려 고치는 게 정석이지만 그 전에 만료 검증을 게이트웨이 정책 레벨에서 직접 걸었습니다.
서명 검증이 알아서 해 주기를 기다리지 말고 exp 를 명시적으로 비교하게 했습니다.
여기서 신경 쓴 건 시계 오차(clock skew)로, 발급자와 게이트웨이의 시계가 몇 초 어긋날 수 있어서
딱 exp 순간에 자르면 멀쩡한 토큰이 억울하게 거부됩니다. 시계 오차만큼 tolerance 를 두고,
그만큼을 넘겨 만료된 토큰은 거부하게 했습니다. 고친 뒤 검증할 때는 TTL 만 짧게 잡는 걸로는
부족하고, tolerance 를 넘길 만큼 지난 토큰을 던져야 합니다.
“검증한다”의 단계 — 서명·iss·aud·exp·nbf
“게이트웨이가 JWT 를 검증한다” 는 말은 생각보다 뭉뚱그린 표현입니다. JWT 검증은 여러 단계입니다
— 서명이 맞는지, 발급자가 맞는지(iss), 대상이 맞는지(aud), 만료 안 됐는지(exp), 아직
유효 시작 전은 아닌지(nbf).
이 중 하나만 빠져도 “검증한다” 는 맞는 말이지만 보안 구멍이 생깁니다. 서명만 확인하고 exp 를
안 보면 한 번 발급된 토큰은 서명이 유효한 한 영원히 통과합니다.
인증을 외부 컴포넌트(게이트웨이·프록시)에 맡길 때는 “검증한다” 를 믿지 말고 만료된 토큰이 실제로 거부되는지 를 직접 테스트해봐야 합니다. TTL 을 아주 짧게 발급해서 만료된 뒤에 호출해보는 게 제일 확실합니다.
댓글 0