게이트웨이 JWT 검증 — exp 누락
API 앞에 인증 게이트웨이를 두고 JWT 검증을 거기서 하게 해뒀습니다. 서비스마다 토큰 검사 코드를 넣지 않아도 되고, 인증 로직이 한 곳에 모인다는 게 이 구조를 쓰는 이유입니다.
그런데 만료된 토큰으로 호출해도 그대로 통과했습니다. 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 모드에서도 무시합니다. 인증 모드를 Strict 로 두든 Permissive 로
두든 똑같이 exp 를 무시했습니다. 설정 문제가 아니었습니다.
즉 게이트웨이가 서명은 확인하는데 exp 클레임 검증만 빠져 있거나 버그로
동작 안 하는 상태였습니다. 특정 버전의 문제였고, 그 버전에서는 아무리
설정해도 만료 검증이 안 됐습니다.
만료 검증을 게이트웨이 정책에서 직접
버전을 올려 고치는 게 정석이지만, 그 전에 만료 검증을 게이트웨이 정책 레벨에서
직접 걸었습니다. 서명 검증에 의존하지 말고, exp 를 명시적으로 비교하게 한
겁니다.
여기서 하나 신경 쓴 건 시계 오차(clock skew)입니다. 발급자와 게이트웨이의
시계가 몇 초 어긋날 수 있어서, 딱 exp 순간에 자르면 정상 토큰이 억울하게
거부될 수 있습니다. 그래서 60초 정도의 tolerance 를 뒀습니다. exp 를 60초
넘겨서 만료된 것만 거부합니다.
“검증한다”의 단계 — 서명·iss·aud·exp·nbf
“게이트웨이가 JWT 를 검증한다” 는 말은 생각보다 뭉뚱그려진 표현입니다. JWT
검증은 여러 단계입니다 — 서명이 맞는지, 발급자가 맞는지(iss), 대상이
맞는지(aud), 만료 안 됐는지(exp), 아직 유효 시작 전은 아닌지(nbf).
이 중 하나만 빠져도 “검증한다” 는 맞는 말이지만 보안 구멍이 생깁니다.
서명만 확인하고 exp 를 안 보면, 한 번 발급된 토큰은 서명이 유효한 한
영원히 통과합니다.
인증을 외부 컴포넌트(게이트웨이·프록시)에 맡길 때는 “검증한다” 를 믿지 말고, 만료된 토큰이 실제로 거부되는지 를 직접 테스트해봐야 합니다. TTL 을 아주 짧게 발급해서 만료 후 호출해보는 것, 이게 제일 확실한 확인입니다.
댓글 0