13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] 인증 오류

  • [Infra] SSO 로그인 장애 발생 시 디버깅 체크리스트와 해결 전략

    [Infra] SSO 로그인 장애 발생 시 디버깅 체크리스트와 해결 전략

    [Infra] SSO 로그인 장애 발생 시 디버깅 체크리스트와 해결 전략

    운영 중인 서비스에서 갑자기 로그인이 안 되기 시작하면, 특히 SSO 로그인 장애 해결 이슈는 생각보다 훨씬 까다롭게 번진다. 사용자 입장에서는 그냥 “로그인이 안 된다”로 끝나지만, 운영자 입장에서는 IdP(Identity Provider, 인증 제공자), SP(Service Provider, 서비스 제공자), 브라우저 쿠키, 리버스 프록시(reverse proxy, 역방향 프록시), 시간 동기화까지 전부 의심해야 하거든요. 저도 처음엔 이게 뭔가 싶었는데, 실제로 장애 대응을 몇 번 해보니까 결국 핵심은 감으로 때려 맞추는 게 아니라 체크리스트 기반으로 좁혀가는 것이더라고요. 이번 글에서는 제가 현장에서 자주 쓰는 SSO 디버깅 순서와 IDP 문제 해결 전략을 정리해보겠다.

    사용자, 브라우저, 서비스 제공자, 인증 제공자 사이에서 어디서 실패하는지 한눈에 파악하는 구조도입니다.

    1. 왜 SSO 로그인 장애는 빨리 커질까요?

    SSO(Single Sign-On, 통합 로그인)는 쉽게 말해 한 번 인증하면 여러 서비스에 연동되는 구조입니다. 평소에는 정말 편합니다. 그런데 장애가 나면 영향 범위도 같이 커집니다. 한 서비스만 막히는 게 아니라, 회사 포털, 내부 위키, VPN, 협업 도구까지 줄줄이 막힐 수 있거든요.

    실제로 써보니까 SSO 장애는 보통 아래 네 가지 패턴으로 나뉘더라고요.

    • 리다이렉트(redirect, 재전송) 루프: 로그인 페이지로 계속 돌아감
    • 인증 오류: 잘못된 Assertion, invalid token, audience mismatch 같은 오류
    • 세션 문제: 로그인 직후 다시 로그아웃되거나 세션이 안 붙음
    • 환경 문제: DNS, TLS 인증서, 프록시 헤더, 서버 시간 차이

    여기서 중요한 포인트! SSO는 애플리케이션만 봐서는 잘 안 풀립니다. 브라우저 – 네트워크 – 애플리케이션 – IdP를 한 묶음으로 봐야 합니다.

    2. SSO 로그인 흐름 이해하기: 구조를 먼저 머릿속에 그려보세요

    저도 처음엔 로그만 뒤졌었는데, 사실 그 전에 흐름부터 정리해야 하더라고요. 쉽게 말해 사용자가 보호된 페이지에 접근하면 SP가 “너 인증 필요해”라고 판단하고 IdP로 보냅니다. 사용자가 IdP에서 로그인하면, IdP는 SAML Assertion(사설명서 같은 인증 응답)이나 OIDC Token(JSON 기반 토큰)을 SP로 돌려보냅니다. 그다음 SP가 검증하고 세션을 발급하면 끝입니다.

    항목 SAML OIDC
    주요 데이터 XML Assertion ID Token, Access Token
    전송 방식 브라우저 리다이렉트, POST Authorization Code Flow 등
    운영 중 자주 보는 문제 서명, ACS URL, NameID 불일치 redirect_uri, issuer, nonce, scope 오류
    확인 포인트 메타데이터, 인증서, 시간 클라이언트 설정, 토큰 클레임

    이 흐름을 기준으로 보면 SSO 로그인 장애 지점이 꽤 명확해집니다. 브라우저가 못 가는지, IdP가 거절하는지, SP가 응답을 못 읽는지 구분이 되거든요.

    3. SSO 로그인 장애 해결 체크리스트: 제가 가장 먼저 보는 순서

    이 부분은 진짜 실전입니다. 삽질 좀 했습니다 ㅎㅎ 그래서 지금은 무조건 아래 순서로 봅니다.

    1. 사용자 증상 수집: 전원 장애인지, 특정 사용자만 그런지, 특정 브라우저만 그런지 확인합니다.
    2. 최근 변경사항 확인: 인증서 교체, 프록시 설정 변경, 도메인 변경, 쿠키 정책 수정이 있었는지 봅니다.
    3. 브라우저 개발자 도구 확인: Network 탭에서 302, 400, 401, 403 흐름을 추적합니다.
    4. 애플리케이션 로그 확인: assertion invalid, token verification failed, audience mismatch 같은 문자열을 찾습니다.
    5. IdP 로그 확인: 정책 차단인지, 앱 설정 mismatch인지 확인합니다.
    6. 시간 동기화 점검: NTP(Network Time Protocol, 시간 동기화) 오차가 몇 분만 나도 실패합니다.
    7. 프록시/로드밸런서 헤더 확인: X-Forwarded-Proto, Host 헤더가 꼬이면 redirect_uri가 달라집니다.

    운영 서버에서 빠르게 볼 때는 이런 식으로 확인합니다.

    # 애플리케이션 로그에서 인증 관련 에러만 추리기
    journalctl -u myapp -n 300 | egrep -i "saml|oidc|oauth|token|assertion|redirect|issuer|audience|nonce|session"
    
    # NTP 동기화 상태 확인
    chronyc tracking
    chronyc sources -v
    
    # 리다이렉트와 응답 헤더 확인
    curl -k -I https://service.example.com/login
    curl -k -L -v https://service.example.com/login
    
    # 인증서 만료일 확인
    openssl s_client -connect service.example.com:443 -servername service.example.com </dev/null | openssl x509 -noout -dates -issuer -subject

    직접 해보니 여기서 절반은 걸러집니다. 특히 최근 변경사항과 시간 동기화는 생각보다 자주 원인이 됩니다.

    4. 실전 구현: 로그와 설정으로 원인 좁히기

    4-1. OIDC 설정 점검 포인트

    OIDC(OpenID Connect, OpenID 기반 인증 확장)를 쓰는 서비스라면 클라이언트 설정이 맞는지부터 보셔야 합니다. redirect_uri 하나만 달라도 바로 로그인 실패가 납니다.

    auth:
      oidc:
        issuer: "https://idp.example.com/realms/main"
        client_id: "internal-portal"
        client_secret: "REDACTED"
        redirect_uri: "https://portal.example.com/oauth/callback"
        scopes:
          - openid
          - profile
          - email

    여기서 제가 실제로 자주 본 문제는 세 가지였습니다.

    • issuer 불일치: 트레일링 슬래시 하나 차이로 검증 실패
    • redirect_uri 불일치: 프록시 뒤에 있어서 http로 인식됨
    • scope 누락: email, profile이 없어 사용자 매핑 실패

    4-2. SAML 설정 점검 포인트

    SAML은 XML 기반이라 더 고전적인 대신, 장애가 나면 더 난감할 때가 있습니다. 처음엔 에러 메시지도 불친절해서 헷갈렸는데, 결국 볼 건 비슷합니다.

    saml:
      entity_id: "https://service.example.com/saml/metadata"
      acs_url: "https://service.example.com/saml/acs"
      idp_metadata_url: "https://idp.example.com/metadata"
      want_assertions_signed: true
      want_response_signed: true

    ACS URL(Assertion Consumer Service URL, 인증 응답 수신 주소), Entity ID, 서명용 인증서가 안 맞으면 거의 바로 터집니다.

    SSO 로그인 장애 해결에 필요한 OIDC와 SAML 설정 비교 이미지

    실무에서 자주 확인하는 issuer, redirect URI, ACS URL, Entity ID 같은 핵심 항목을 비교해 보여주는 이미지입니다.

    5. ⚠️ 자주 만나는 장애 패턴과 해결 전략

    이제부터는 제가 장애 대응하면서 반복해서 본 케이스들입니다. 여기서 시간 많이 씁니다.

    5-1. 무한 리다이렉트가 걸릴 때

    브라우저에서 로그인 후 다시 로그인 페이지로 돌아오면 보통 세션이 저장되지 않았거나, 콜백 URL 계산이 틀린 경우가 많습니다.

    • 쿠키의 SameSite 속성 확인
    • Secure 쿠키인데 HTTPS 종료 지점이 꼬이지 않았는지 확인
    • X-Forwarded-Proto, X-Forwarded-Host 헤더 전달 확인
    • 애플리케이션의 external URL 설정 확인

    저는 예전에 로드밸런서에서 HTTPS를 종료하고 백엔드로 HTTP를 넘기는 구조에서 이걸 크게 겪었습니다. 앱은 자꾸 자기 주소를 http로 계산하고, IdP에는 https로 등록되어 있으니 매번 검증이 틀어지더라고요.

    5-2. token expired 또는 not yet valid

    이건 거의 시간 문제입니다. 서버 두 대 중 한 대만 시간이 어긋나도 간헐 장애처럼 보입니다. 그래서 모든 인증 노드는 반드시 같은 시간 기준을 써야 합니다.

    timedatectl status
    chronyc tracking
    # 컨테이너 환경이라면 호스트 시간도 같이 확인

    5-3. 특정 사용자만 실패할 때

    이 경우는 그룹 매핑(group mapping), 이메일 클레임(claim), NameID 형식, 역할(role) 동기화 문제일 때가 많습니다. 즉, 시스템 전체 장애가 아니라 속성(attribute) 매핑 문제일 수 있습니다.

    5-4. 인증서는 멀쩡한데 서명 검증이 실패할 때

    이건 IdP 메타데이터가 갱신됐는데 SP가 예전 인증서를 계속 들고 있을 때 자주 봅니다. 메타데이터 캐시를 갱신하거나, 인증서를 다시 가져오면 풀리는 경우가 많습니다.

    6. 검증 절차: 수정 후에는 이렇게 확인합니다

    SSO 로그인 장애 조치가 끝났다고 바로 종료하면 안 됩니다. 저도 예전에 한 사용자만 테스트하고 끝냈다가, 다른 브라우저에서 다시 터진 적이 있었습니다. 그래서 수정 후 검증은 아래처럼 분리해서 합니다.

    1. 신규 세션 테스트: 시크릿 모드에서 처음부터 로그인
    2. 기존 세션 테스트: 로그인 상태 유지, 로그아웃 후 재로그인
    3. 권한별 테스트: 일반 사용자, 관리자, 외부 사용자 계정
    4. 브라우저별 테스트: Chrome, Edge, Safari 등
    5. 로그 검증: 에러가 사라졌는지, 경고만 남았는지 확인
    # 최근 10분간 에러 로그 재확인
    journalctl -u myapp --since "10 minutes ago" | egrep -i "error|warn|saml|oidc|oauth|token|assertion"
    
    # 헬스체크 응답 확인
    curl -k https://service.example.com/health
    
    # 로그인 후 콜백 응답 코드 확인 예시
    curl -k -I https://portal.example.com/oauth/callback
    SSO 로그인 장애 해결 후 정상 동작을 확인하는 결과 대시보드 이미지

    장애 조치 이후 정상 로그인과 에러 감소 추이를 함께 보여주는 검증 결과 이미지입니다.

    이렇게 해두면 단순히 “된다”가 아니라, 왜 해결됐는지까지 남길 수 있습니다. 이게 다음 장애 때 큰 차이를 만듭니다. 드디어 됐다! 싶은 순간이 오더라고요.

    7. 빠르게 보는 SSO 디버깅 체크리스트

    운영 중 급할 때 바로 볼 수 있게 요약하면 이렇습니다.

    체크 항목 무엇을 볼까 의심 원인
    브라우저 Network 302/400/401/403 흐름 redirect_uri, 쿠키, 프록시
    애플리케이션 로그 issuer, audience, nonce, assertion 설정 불일치, 서명 오류
    IdP 로그 정책 차단, 매핑 실패 사용자 속성, 그룹, 앱 정책
    시간 동기화 NTP 상태 token expired, not yet valid
    TLS/인증서 만료일, 체인 신뢰 실패, 메타데이터 불일치

    SSO 로그인 장애 해결을 할 때 중요한 건 도구보다 순서입니다. 순서만 잡히면 장애 대응 시간이 확 줄어듭니다.

    8. 정리와 다음 단계

    오늘 정리한 내용은 결국 하나로 모입니다. SSO 디버깅은 인증 서버만 보지 말고, 사용자 요청이 지나가는 전 구간을 따라가야 한다는 점입니다. 저도 처음엔 앱 로그만 붙잡고 있었는데, 실제로는 프록시 헤더 하나 때문에 반나절을 날린 적도 있었거든요. 그런 삽질을 몇 번 하고 나니, 이제는 무조건 체크리스트로 갑니다.

    혹시 지금 인증 오류나 로그인 실패 때문에 급하게 보고 계시다면, 먼저 최근 변경사항과 시간 동기화부터 보세요. 그다음 브라우저 리다이렉트 흐름, 애플리케이션 로그, IdP 로그 순으로 좁혀가면 됩니다. 이 흐름만 익숙해져도 IDP 문제 해결 속도가 꽤 빨라집니다.

    다음 글에서는 Keycloak, Okta 같은 실존 IdP 제품에서 공통적으로 확인할 수 있는 로그 포인트와, Kubernetes(쿠버네티스) Ingress(인그레스, 외부 트래픽 진입점) 뒤에서 SSO가 꼬일 때의 대응법도 다뤄볼 예정입니다. 이전 글에서 다뤘던 리버스 프록시 헤더 정리 글도 함께 보시면 더 이해가 잘 되실 겁니다.

    SSO 로그인 장애 해결 체크리스트 요약 인포그래픽

    장애 대응 순서, 주요 원인, 확인 명령어를 한 장으로 요약한 인포그래픽 이미지입니다.

    9. 자주 묻는 질문

    Q1. 로그인 실패가 특정 브라우저에서만 발생하면 어디를 봐야 하나요?

    쿠키 정책, 캐시, 확장 프로그램, 서드파티 쿠키 차단 설정부터 보시면 됩니다. 특히 SameSite 정책은 브라우저별 체감이 다를 수 있습니다.

    Q2. IdP는 정상인데 서비스만 로그인 안 되면요?

    SP 쪽 redirect_uri, ACS URL, issuer, audience 설정을 다시 보셔야 합니다. 대부분은 설정 불일치입니다.

    Q3. 장애 재발 방지는 어떻게 하시나요?

    설정 변경 전후 diff 관리, 메타데이터 만료 모니터링, NTP 상태 점검, synthetic login test(합성 로그인 테스트) 자동화를 추천드립니다. 이거 진짜 편하더라고요.

  • [백엔드] JWT 오류 해결: 만료와 서명 검증 디버깅 가이드

    [백엔드] JWT 오류 해결: 만료와 서명 검증 디버깅 가이드

    [백엔드] JWT 오류 해결: 만료와 서명 검증 디버깅 가이드

    JWT 오류 해결은 인증 기능을 붙이는 순간 한 번쯤은 꼭 마주치게 되는 주제입니다. 로그인까지는 잘 되는데 API 호출에서 갑자기 401이 떨어지거나, 분명 같은 비밀 키(secret key)를 쓴다고 생각했는데 JWT 서명 검증 단계에서 실패하는 경우가 있거든요. 저도 처음엔 이게 뭔가 싶었습니다. 토큰 하나 던졌을 뿐인데, 어디서 깨졌는지 감이 안 오더라고요. 실제로 써보니까 JWT는 단순해 보여도 시간 동기화, 알고리즘(algorithm), 키 관리, 클레임(claim) 검증 포인트가 엮여 있어서 삽질하기 딱 좋습니다 ㅎㅎ 이번 글은 제가 현업과 홈랩에서 겪었던 패턴을 기준으로, 토큰 만료부터 JWT 디버깅, 그리고 인증 오류 원인 분리까지 한 번에 정리해보겠습니다.

    특히 백엔드 API, 게이트웨이(gateway), 리버스 프록시(reverse proxy), 모바일 앱 백엔드 연동에서 자주 나오는 증상을 중심으로 설명할게요. 혹시 “토큰은 있는데 왜 인증이 안 되지?” 같은 상황을 겪고 계셨다면, 이 글 순서대로 보시면 꽤 빠르게 원인을 좁힐 수 있습니다.

    JWT 오류 해결을 위한 인증 흐름과 오류 지점 개요 이미지

    클라이언트, API 서버, 인증 서버 사이에서 JWT가 이동하고 만료, 서명 검증, 클레임 검증이 어디서 실패하는지 보여주는 개요 이미지입니다.

    JWT 오류 해결 전에 먼저 보는 전체 흐름

    쉽게 말해 JWT(JSON Web Token)는 서명된 주장 묶음입니다. 서버가 “이 사용자는 누구고, 언제까지 유효하며, 어떤 권한이 있다”라는 정보를 토큰에 담아서 보내고, 이후 요청에서는 DB 조회를 최소화한 채 토큰만 검증하는 방식이죠.

    여기서 중요한 포인트가 있습니다. JWT 검증은 보통 아래 순서로 흘러갑니다.

    1. Authorization 헤더에서 Bearer 토큰 추출
    2. 토큰 형식 파싱
    3. 헤더(header)의 알고리즘 확인
    4. 서명(signature) 검증
    5. exp, nbf, iat 같은 시간 기반 클레임 검증
    6. iss, aud, sub 등 발급자/대상 클레임 검증
    7. 애플리케이션 권한 검사

    문제는 에러 로그가 이 순서를 친절하게 설명해주지 않는 경우가 많다는 점입니다. 그냥 invalid token 한 줄로 끝나는 프레임워크도 있거든요. 그래서 저는 늘 “지금 실패한 단계가 파싱인지, 시간 검증인지, 서명 검증인지”부터 분리합니다. 이걸 먼저 나누면 절반은 끝난 셈입니다.

    JWT 디버깅에 필요한 핵심 개념 정리

    1. exp, nbf, iat는 시간이 핵심입니다

    exp(expiration, 만료 시각)는 토큰 만료 시점이고, nbf(not before, 이 시각 이전엔 사용 불가)는 활성 시작 시점, iat(issued at, 발급 시각)는 발급 시간입니다. 여기서 서버 시간이 어긋나면 정상 토큰도 떨어져버린다는 게 또 다른 함정입니다. NTP(Network Time Protocol, 시간 동기화) 안 맞아서 생기는 문제가 은근 많더라고요.

    2. 서명 검증은 문자열 하나만 달라도 깨집니다

    JWT 서명 검증은 “같은 알고리즘과 같은 키로 서명했는지”를 확인하는 과정입니다. HS256 같은 HMAC 대칭키 방식은 발급 서버와 검증 서버가 같은 비밀 값을 알아야 하고, RS256 같은 RSA 비대칭키 방식은 개인키(private key)로 서명하고 공개키(public key)로 검증합니다. 여기서 환경 변수 공백, 줄바꿈, Base64 인코딩 처리 차이만 있어도 실패하더라고요. 저도 PEM 키 붙여넣기 잘못해서 한참 헤맸습니다.

    3. 클레임 검증 실패도 인증 오류로 보입니다

    토큰 자체는 멀쩡한데 iss(issuer, 발급자) 값이 다르거나 aud(audience, 대상 서비스) 검증 기준이 맞지 않아서 막히는 경우가 있습니다. 로그에는 그냥 401로만 보이니까 서명 오류로 오해하기 쉽습니다.

    증상 가능한 원인 우선 확인할 것
    로그인 직후 401 서버 시간 어긋남, nbf 문제 서버 시간, exp/nbf 값
    특정 서버에서만 실패 키 불일치, 환경 변수 차이 배포 환경 키 값, 알고리즘 설정
    개발 환경은 되는데 운영만 실패 프록시 헤더 누락, 시크릿 차이 운영 ENV, 프록시 설정
    토큰 갱신 후 바로 실패 구 토큰 사용, 캐시 문제 클라이언트 저장 토큰, refresh 흐름
    간헐적 실패 멀티 인스턴스 키 불일치 인스턴스별 설정 동일성

    실전 구현: JWT 디버깅 체크리스트와 재현 방법

    이제부터는 제가 실제로 많이 쓰는 점검 순서입니다. 프레임워크가 무엇이든 개념은 비슷합니다. 핵심은 토큰을 눈으로 확인하고, 서버가 기대하는 값과 비교하는 겁니다.

    1. 토큰 구조 먼저 확인합니다

    JWT는 보통 <code>header.payload.signature 세 덩어리로 구성됩니다. 가장 먼저 토큰이 잘려서 전달됐는지부터 봅니다.

    TOKEN="eyJ...생략..."\npython3 - <<'PY'\nimport base64, json, sys\n\ntoken = sys.argv[1] if len(sys.argv) > 1 else ""\nparts = token.split('.')\nprint(f"parts={len(parts)}")\nif len(parts) != 3:\n    print("invalid jwt format")\n    raise SystemExit(1)\n\ndef decode(part):\n    part += '=' * (-len(part) % 4)\n    return json.loads(base64.urlsafe_b64decode(part.encode()).decode())\n\nprint("header=", decode(parts[0]))\nprint("payload=", decode(parts[1]))\nPY "$TOKEN"

    여기서 헤더의 alg, 페이로드의 exp, nbf, iss, aud 정도는 바로 보셔야 합니다. 저는 이 단계에서 생각보다 많은 문제를 잡았습니다. 토큰 앞뒤 공백이 붙어 있거나, 아예 다른 서비스 토큰이 들어오는 경우도 있거든요.

    2. 현재 서버 시간과 만료 시간을 같이 봅니다

    date -u\npython3 - <<'PY'\nimport datetime\nexp = 1735689600\nprint(datetime.datetime.utcfromtimestamp(exp).isoformat() + "Z")\nPY

    토큰 만료 문제는 단순합니다. 지금 UTC 시간이 exp 이후면 실패입니다. 그런데 실제 운영에서는 단순하지 않더라고요. 컨테이너는 맞는데 호스트 시간이 밀려 있거나, 한 대만 시간이 어긋난 경우가 있었습니다. 특히 오토스케일링(auto scaling) 환경에서는 특정 인스턴스에서만 실패하는 식으로 보일 수 있습니다.

    3. 서버 검증 코드에 로그 포인트를 나눕니다

    프레임워크가 에러를 뭉뚱그려서 던지면 직접 단계별 로그를 넣는 게 빠릅니다. 예시는 Python으로 보겠습니다.

    import jwt\nfrom jwt import ExpiredSignatureError, InvalidSignatureError, InvalidTokenError\n\nSECRET = "replace-with-real-secret"\nALGORITHM = "HS256"\n\ndef verify_token(token: str):\n    try:\n        payload = jwt.decode(\n            token,\n            SECRET,\n            algorithms=[ALGORITHM],\n            options={"require": ["exp", "iat"]}\n        )\n        return {"ok": True, "payload": payload}\n    except ExpiredSignatureError:\n        return {"ok": False, "stage": "expiration", "message": "token expired"}\n    except InvalidSignatureError:\n        return {"ok": False, "stage": "signature", "message": "signature verification failed"}\n    except InvalidTokenError as e:\n        return {"ok": False, "stage": "generic", "message": str(e)}

    이렇게만 해도 로그가 훨씬 읽히기 좋아집니다. 처음엔 귀찮아 보이는데, 장애 대응 때 이 차이가 큽니다. “만료인지 서명인지”가 바로 보이니까요.

    JWT 디버깅과 토큰 만료 확인 과정을 보여주는 이미지

    토큰을 디코딩해 header, payload를 확인하고 검증 단계를 나눠 보는 실전 디버깅 흐름을 보여주는 이미지입니다.

    4. 키와 알고리즘이 맞는지 분리해서 확인합니다

    가장 흔한 실수 중 하나가 HS256으로 발급했는데 검증 쪽에서 RS256을 기대하거나, 반대로 공개키 기반 토큰인데 문자열 시크릿으로 검증하려는 경우입니다. 쉽게 말해 알고리즘 타입이 다르면 절대 통과하지 않습니다.

    echo "$JWT_SECRET" | wc -c\nprintf '%s' "$JWT_SECRET" | sha256sum

    운영 중에는 시크릿 원문을 로그로 남기면 안 되니까, 저는 길이와 해시만 비교합니다. 멀티 서버에서 같은 해시가 나오는지 보면 키 불일치를 꽤 안전하게 확인할 수 있습니다.

    5. 리버스 프록시와 헤더 전달도 확인합니다

    Nginx나 Ingress(인그레스, 외부 트래픽 진입점) 뒤에 있을 때는 Authorization 헤더가 빠지는 경우도 있습니다. 이럴 땐 JWT 문제가 아니라 프록시 설정 문제죠.

    apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: api\nspec:\n  rules:\n    - host: example.local\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: api-service\n                port:\n                  number: 80

    구성 자체는 단순해 보여도, 중간 프록시나 API 게이트웨이에서 헤더 재작성(rewrite) 규칙이 있으면 여기서 틀어집니다. 저는 curl로 직접 헤더를 날려보는 방식으로 먼저 분리합니다.

    curl -i https://api.example.local/me \\\n  -H "Authorization: Bearer $TOKEN"

    ⚠️ 실제로 많이 겪는 JWT 인증 오류 패턴

    여기부터는 제가 자주 봤던 케이스입니다. 정말 많이 나옵니다.

    토큰 만료인데 프론트엔드가 조용히 재시도만 하는 경우

    401이 나왔는데 클라이언트가 refresh token(리프레시 토큰) 갱신 실패를 숨기고 같은 요청만 반복하면, 서버에서는 그냥 인증 오류만 잔뜩 찍힙니다. 사용자는 “갑자기 느리다”고 느끼고요. 네트워크 탭에서 access token(액세스 토큰) 갱신 요청이 실제로 성공했는지 꼭 보셔야 합니다.

    시크릿 값 앞뒤 공백 문제

    이거 진짜 흔합니다. 쿠버네티스 시크릿(Kubernetes Secret), CI/CD 변수, .env 파일 옮기는 과정에서 줄바꿈이 끼어들면 JWT 서명 검증이 계속 실패합니다. 제가 직접 해보니 로컬에선 되는데 운영만 안 되는 황당한 케이스가 대부분 여기 있더라고요.

    서버 간 키 불일치

    로드밸런서(load balancer) 뒤에 인스턴스가 여러 대인데 한 대만 예전 키를 들고 있으면 요청이 간헐적으로 성공/실패합니다. 이런 경우는 사용자가 느끼기에 제일 답답합니다. “아까는 됐는데 지금은 안 돼요” 패턴이거든요.

    iss, aud 검증 기준 누락

    반대로 검증 로직이 너무 느슨해도 문제고, 너무 엄격해도 문제입니다. 예를 들어 외부 인증 공급자(IdP, Identity Provider)에서 발급한 토큰을 받는데 발급자 문자열 비교를 잘못 잡으면 계속 실패합니다.

    오류 메시지 예시 해석 대응
    token expired 만료 시각 초과 서버 시간 확인, 재발급 흐름 점검
    signature verification failed 키 또는 알고리즘 불일치 시크릿/공개키, alg 확인
    invalid audience aud 검증 실패 대상 서비스 값 재검토
    invalid issuer iss 검증 실패 발급자 URL 또는 문자열 비교 확인
    malformed token 형식 손상 헤더 전달, 토큰 잘림 여부 확인
    JWT 서명 검증 실패가 발생하는 다중 서버 구성 이미지

    로드밸런서 뒤 여러 애플리케이션 인스턴스 중 일부만 다른 키를 사용해 JWT 서명 검증이 실패하는 상황을 보여주는 이미지입니다.

    문제 재현이 안 될 때 제가 쓰는 점검 순서

    장애 대응에서 제일 답답한 게 “운영에서는 터지는데 개발에서는 안 터진다”는 상황이죠. 이럴 때는 감으로 보지 않고 체크리스트로 갑니다.

    1. 실패한 원본 토큰 확보: 민감 정보 취급 주의, 로그 마스킹 필수
    2. 토큰 header/payload 디코딩
    3. exp, nbf, iat를 UTC 기준으로 해석
    4. 검증 서버의 실제 시간 확인
    5. alg와 서버 설정 일치 여부 확인
    6. 검증 키 길이/해시 비교
    7. iss, aud 검증 조건 확인
    8. 프록시와 게이트웨이의 Authorization 헤더 전달 확인
    9. 멀티 인스턴스라면 모든 노드 설정 비교

    여기서 중요한 건, 한 번에 다 바꾸지 않는 겁니다. 예전에 제가 급하다고 만료 시간도 늘리고 키도 바꾸고 검증 옵션도 수정했다가, 뭐가 원인이었는지 더 헷갈린 적이 있습니다. 삽질 좀 했습니다 ㅎㅎ 장애 디버깅은 한 변수씩 좁혀야 합니다.

    검증/결과: 정상 동작 기준은 이렇게 잡으면 편합니다

    정상 여부를 판단할 때는 단순히 “로그인이 된다” 수준이면 부족합니다. 아래 항목이 모두 맞아야 안정적입니다.

    • 만료된 토큰은 의도대로 401을 반환합니다
    • 유효한 토큰은 모든 인스턴스에서 동일하게 통과합니다
    • 잘못된 서명 토큰은 반드시 거부됩니다
    • iss, aud가 다른 토큰도 거부됩니다
    • 리프레시 이후 새 토큰으로 정상 요청이 됩니다

    저는 보통 간단한 스모크 테스트(smoke test)를 따로 둡니다. 정상 토큰, 만료 토큰, 다른 키로 서명한 토큰 이렇게 세 종류만 있어도 배포 검증이 훨씬 쉬워집니다. 드디어 됐다! 싶은 순간이 여기서 오더라고요.

    curl -s -o /dev/null -w "%{http_code}\\n" https://api.example.local/me \\\n  -H "Authorization: Bearer $VALID_TOKEN"\n\ncurl -s -o /dev/null -w "%{http_code}\\n" https://api.example.local/me \\\n  -H "Authorization: Bearer $EXPIRED_TOKEN"

    기대 결과는 각각 200, 401처럼 명확해야 합니다. 애매하게 500이 나온다면 검증 예외 처리가 잘못된 겁니다.

    JWT 오류 해결 결과와 인증 오류 검증 화면 이미지

    정상 토큰은 성공, 만료 또는 잘못된 서명 토큰은 실패로 구분되는 검증 결과를 시각적으로 보여주는 이미지입니다.

    FAQ: JWT 오류 해결할 때 자주 받는 질문

    Q1. JWT는 디코딩되는데 왜 인증이 실패하나요?

    디코딩과 검증은 다릅니다. Base64URL 디코딩은 누구나 할 수 있지만, 서명이 유효한지 확인하는 건 별개입니다. payload가 보인다고 유효한 토큰은 아닙니다.

    Q2. exp만 늘리면 해결되지 않나요?

    일시적으로는 나아 보여도 근본 해결은 아닙니다. 시간 동기화, refresh 흐름, 토큰 저장 방식이 꼬였으면 다시 터집니다.

    Q3. 로컬은 되는데 운영만 안 되는 이유는 뭔가요?

    대부분 환경 변수, 프록시 헤더, 멀티 인스턴스 키 불일치, 시간 동기화 문제였습니다. 저도 처음엔 코드 버그만 의심했었는데, 실제론 운영 구성 차이가 더 많았습니다.

    마무리: JWT 디버깅은 단계 분리가 전부입니다

    이번 글에서는 JWT 오류 해결을 위해 토큰 구조 확인, 시간 기반 검증, JWT 서명 검증, 클레임 검사, 프록시 구간 점검까지 순서대로 정리해봤습니다. 정리하면 핵심은 간단합니다. 파싱 오류인지, 토큰 만료인지, 서명 검증 실패인지, 클레임 불일치인지 분리해서 본다. 이 순서만 잡혀도 인증 오류 대응 속도가 꽤 빨라집니다.

    제가 직접 해보니 JWT는 라이브러리 한 줄로 끝나는 기술이 아니라, 운영 환경까지 포함해서 봐야 덜 고생합니다. 특히 홈랩처럼 서버를 이것저것 붙여보는 환경에서는 시간 동기화와 키 배포 방식이 진짜 중요하더라고요. 다음 글에서는 refresh token 회전(rotation) 전략과 로그 마스킹 방법도 다뤄볼 예정입니다. 이전 글에서 다뤘던 리버스 프록시 헤더 전달 점검 내용과 함께 보시면 더 이해가 잘 되실 겁니다.

    JWT 오류 해결 순서를 요약한 인포그래픽 이미지

    파싱, 만료, 서명, 클레임, 프록시 점검 순서를 한눈에 볼 수 있도록 정리한 요약 이미지입니다.

    혹시 지금도 원인을 못 찾고 계시다면, 토큰 자체보다 시간, 키, 헤더 전달 이 세 가지부터 다시 보세요. 여기서 중요한 포인트! 대부분의 삽질은 생각보다 단순한 설정 차이에서 시작합니다. 이 글이 그 시간을 좀 줄여드렸으면 좋겠네요.