운영자님 안녕하세요. 인증 쪽에서 재현이 안 되는 버그 때문에 스트레스 받고 있습니다.
로그인하면 HS256으로 JWT를 발급하고, 이후 요청마다 검증하는 구조입니다. 로컬에서는 100% 정상인데, 쿠버네티스에 replica 3개로 올린 배포 환경에서만 가끔 이런 에러가 납니다.
JsonWebTokenError: invalid signature
at verify (node_modules/jsonwebtoken/verify.js:...)토큰 발급 코드입니다.
// 앱 부팅 시 시크릿 생성
const SECRET = crypto.randomBytes(32).toString('hex');
function sign(user) {
return jwt.sign({ sub: user.id }, SECRET, { expiresIn: '1h' });
}
function verify(token) {
return jwt.verify(token, SECRET);
}같은 토큰인데 어떤 요청은 통과하고 어떤 요청은 invalid signature가 납니다. 만료 시간도 넉넉한데 왜 이럴까요?
댓글 4
안녕하세요. 코드만 봐도 원인이 명확합니다. 시크릿을 앱 부팅 시점에
crypto.randomBytes로 매번 새로 생성하고 있는 게 문제입니다.replica가 3개면 파드마다 서로 다른 SECRET을 갖게 됩니다. 로드밸런서가 요청을 라운드로빈으로 뿌리니까:
invalid signature딱 이래서 간헐적으로 실패하는 겁니다. 로컬은 인스턴스가 하나라 시크릿이 하나뿐이니 항상 성공했던 거고요. 참고로 파드가 재시작돼도 시크릿이 바뀌기 때문에, 무중단 배포 도중에도 기존 발급 토큰이 전부 무효화됩니다.
해결은 모든 인스턴스가 동일한 시크릿을 공유하게 하는 겁니다. 코드에서 랜덤 생성하지 말고 환경변수로 주입하세요.
쿠버네티스라면 시크릿 값을 Secret 리소스에 넣고 env로 마운트하면 됩니다.
이렇게 3개 파드가 같은 값을 바라보면 어느 파드에서 발급/검증하든 서명이 일치합니다.
리프레시 토큰은 localStorage에 두지 않는 걸 권장합니다. localStorage는 자바스크립트로 언제든 읽을 수 있어서 XSS 한 방이면 토큰이 그대로 탈취됩니다. 특히 장기 유효한 리프레시 토큰이 털리면 피해가 훨씬 큽니다.
권장 구성은 이렇습니다.
HttpOnly,Secure,SameSite=Strict(또는 Lax)쿠키로 발급. HttpOnly라 JS가 못 읽으니 XSS로 못 빼갑니다.두 가지만 더 챙기시면 좋습니다.
쿠키 방식이면 CSRF는 신경 써야 하는데,
SameSite=Strict에 리프레시 엔드포인트 경로를path로 좁혀두면 대부분 막힙니다.