인증 구조의 문제는 보통 로그인 순간보다 그 이후의 요청에서 드러납니다. 로그인은 성공하고, 토큰도 발급되고, 쿠키도 내려갔지만 권한 변경·로그아웃·refresh가 끼어드는 순간부터 질문이 달라졌습니다. 이 요청을 인증된 요청으로 판단할 때 최종 기준이 토큰인지, 쿠키인지, 서버 세션인지가 명확해야 했습니다.

불편했던 지점은 토큰 검증 코드의 위치가 아니었습니다. refresh 타이밍, 쿠키 발급과 삭제, 내부 헤더 생성, 권한 변경 이후의 판단 기준이 서로 다른 레이어에 흩어져 있었습니다. 하나의 요청 흐름 안에서 브라우저와의 계약, 서비스 간 내부 계약, 사용자 상태 판단이 섞이면서 인증 정책을 바꿀 때마다 수정 범위와 검증 범위가 함께 커졌습니다.

그래서 출발점은 “토큰을 어디서 파싱할 것인가”가 아니었습니다. 먼저 정해야 했던 것은 어느 레이어가 인증 상태의 최종 판단자가 되어야 하는가였습니다. Client는 요청을 보내는 주체이고, Backend는 도메인 규칙을 처리하는 레이어입니다. 외부 입력을 정리하고 내부 서비스가 믿을 수 있는 형태로 바꾸는 일은 요청의 앞단에 있는 Gateway에 두는 편이 운영과 검증에서 더 단순했습니다.

JWT 검증 위치만 옮겨서는 경계가 바뀌지 않았습니다

처음에는 이 작업을 JWT 검증 위치를 Gateway로 옮기는 문제로 볼 수도 있었습니다. 하지만 그렇게 접근하면 구현 파일은 바뀌어도 인증 경계는 그대로 흐려집니다. 브라우저가 들고 온 값, Gateway가 계산한 값, Backend가 신뢰할 값이 같은 이름으로 섞여 있으면 장애가 났을 때 결국 다시 전체 흐름을 함께 추적해야 합니다.

그래서 기준을 토큰 처리 위치가 아니라 신뢰 경계로 옮겼습니다.

  • 토큰을 파싱하는 위치보다 최종 인증 상태를 말할 수 있는 레이어를 먼저 정합니다.
  • 브라우저에 내려가는 쿠키와 내부 서비스로 전달되는 인증 헤더를 같은 계약으로 취급하지 않습니다.
  • Backend는 토큰과 쿠키를 직접 해석하기보다 Gateway가 정리한 인증 결과를 소비합니다.
  • 세션 전환 중에는 기존 토큰 경로를 남겨 전환 리스크를 줄입니다.

이 관점에서 Gateway 전환의 핵심은 코드 위치가 아니라, 외부 입력을 내부 서비스가 신뢰할 수 있는 계약으로 바꾸는 지점을 명확히 하는 일이었습니다.

기존 구조의 문제는 코드 중복보다 책임 혼합이었습니다

Gateway 인증 책임 경계
Gateway 인증 책임 경계

기존 구조에서 Backend는 인증의 중심에 있었습니다. 요청이 들어오면 토큰을 읽고, 검증하고, 사용자 컨텍스트를 만들고, 컨트롤러와 서비스는 그 결과를 전제로 동작했습니다. 단순한 API 서버라면 이 방식도 충분히 자연스럽습니다.

문제는 인증이 토큰 검증 하나로 끝나지 않는다는 점이었습니다. 웹 요청에서는 access token과 refresh token을 어떤 쿠키 속성으로 내려야 했고, 별도 진입 경로에서는 만료된 인증 정보를 어느 응답으로 돌려줄지도 달랐습니다. 조직 초대, SSO, 테스트용 진입점처럼 예외 흐름이 붙을수록 인증 후처리는 필터 하나의 책임을 넘어섰습니다.

이 상태에서 정책을 바꾸면 필터, 컨트롤러, 쿠키 처리 코드, 예외 응답, 테스트 코드가 함께 흔들렸습니다. 더 불편했던 것은 파일 수가 아니라 판단과 표현이 같은 위치에 섞여 있다는 점이었습니다. 어떤 코드는 “이 사용자를 인증된 사용자로 볼 수 있는가”를 판단했고, 어떤 코드는 “브라우저에 어떤 쿠키를 내려야 하는가”를 처리했지만, 실제 코드 경계에서는 둘이 분리되어 보이지 않았습니다.

Gateway는 요청의 가장 바깥에서 쿠키, 토큰, 헤더를 다룹니다. 외부에서 들어온 헤더를 정리하고, 내부 서비스로 보낼 값을 다시 구성하고, 응답의 Set-Cookie도 최종적으로 통제할 수 있습니다. 반대로 Backend는 비즈니스 규칙을 처리하는 안쪽 레이어입니다. 쿠키 정책과 refresh 판단이 Backend 안쪽까지 들어오면 인증 정책을 바꿀 때마다 도메인 코드까지 같이 흔들립니다.

전환 전에 신뢰 경계를 먼저 정했습니다

Gateway로 인증을 옮긴다고 해서 복잡도가 사라지지는 않습니다. 위치만 바꾸면 오히려 Gateway가 거대한 인증 필터가 될 수 있습니다. 세션 조회 실패, 토큰만 존재하는 요청, refresh 직후 권한이 바뀌는 요청, 로그아웃 직후 들어오는 요청을 모두 한 지점에서 설명할 수 있어야 했습니다.

그래서 구현 전에 신뢰 경계를 먼저 고정했습니다.

  • Client는 인증 결과를 직접 계산하지 않습니다.
  • Gateway는 요청마다 인증 상태를 계산합니다.
  • Backend는 Gateway가 전달한 내부 헤더 계약만 신뢰합니다.
  • 외부 요청에 포함된 내부 인증 헤더는 Gateway에서 제거한 뒤 다시 주입합니다.
  • 세션 쿠키가 있는 요청은 서버 세션을 우선하고, 세션이 없는 요청은 기존 토큰 경로로 처리합니다.

이 경계를 코드로 옮기기 전에 계약이 흐려졌을 때의 실패 모드도 함께 정리했습니다. 인증 전환에서 위험한 지점은 “검증 로직이 없다”가 아니라 서로 다른 레이어가 같은 요청을 다른 기준으로 판단하는 상황에 가까웠기 때문입니다.

계약담당 레이어흐려졌을 때 생기는 문제
브라우저 계약Gateway쿠키 발급·삭제 기준이 API별로 달라지고, 로그아웃 이후에도 브라우저 상태가 남을 수 있습니다.
내부 헤더 계약Gateway → Backend외부 요청이 만든 헤더와 Gateway가 만든 헤더를 구분하기 어려워집니다.
인증 상태 계약Gateway만료, 누락, 위조, 권한 변경이 모두 비슷한 401로 보이며 원인 추적이 늦어집니다.
도메인 권한 판단BackendBackend가 쿠키와 refresh 세부 정책까지 알아야 해 도메인 코드가 인증 구현에 묶입니다.

이 기준을 잡지 않으면 인증 판단을 Gateway로 옮겨도 내부 신뢰 경계는 그대로 흐려집니다. 클라이언트가 임의로 내부 인증 헤더를 붙여 보냈는데 Backend가 그것을 믿는다면, Gateway 전환은 껍데기만 바뀐 셈입니다. Gateway는 외부 입력을 한 번 정리하고, 자신이 계산한 인증 정보만 내부 서비스에 전달해야 했습니다.

또 하나의 원칙은 기존 토큰 경로를 전환 초기에 제거하지 않는 것이었습니다. 인증 구조를 바꾸는 작업은 성공했을 때보다 실패했을 때 영향이 큽니다. 세션 쿠키가 있는 요청은 Redis 세션을 기준으로 판단하고, 세션 쿠키가 없는 요청은 기존 토큰 경로로 처리했습니다. 이렇게 두 경로를 분리해두면 전환 중에도 기존 사용자의 요청 흐름을 유지할 수 있고, 문제가 생겼을 때 세션 경로인지 기존 토큰 경로인지 로그로 먼저 좁힐 수 있습니다.

쿠키는 브라우저 계약이고, 내부 헤더는 서비스 간 계약입니다

첫 번째로 분리한 것은 쿠키 관리였습니다. 이전에는 인증 성공 이후 access token과 refresh token을 어떤 도메인과 경로로 내려야 하는지에 대한 판단이 Backend 응답 흐름 안에 남아 있었습니다. 기능적으로는 가능했지만 오래 유지하기 좋은 구조는 아니었습니다. 브라우저와 직접 계약하는 쿠키 정책을 도메인 로직에 가까운 레이어가 함께 쥐고 있었기 때문입니다.

그래서 Backend는 쿠키를 직접 만들지 않도록 바꿨습니다. 대신 Gateway가 이해할 수 있는 응답 신호만 남기고, Gateway가 그 신호를 실제 쿠키 발급이나 삭제로 바꾸도록 했습니다.

이 변화의 의미는 코드 위치 이동보다 계약 분리에 있습니다. Backend는 “인증 결과로 무엇을 내려야 하는가”까지만 말하고, Gateway는 그것을 실제 브라우저 응답으로 변환합니다. 이렇게 나누면 쿠키 쓰기 책임이 컨트롤러와 서비스 곳곳에 매달리지 않습니다.

구조를 나누고 나니 달라진 지점은 두 가지였습니다.

  • Backend는 인증 성공 이후의 도메인 판단만 남기고, 쿠키 발급과 삭제 자체는 내려놓을 수 있었습니다.
  • Gateway는 응답 Set-Cookie를 통제하는 위치에서 쿠키 정책을 일관되게 적용할 수 있었습니다.

반대로 Gateway가 사용하는 signal은 외부 클라이언트에 그대로 노출되지 않는 내부 계약으로만 남겼습니다. 쿠키는 브라우저 계약이고, 내부 헤더는 서비스 간 계약입니다. 둘을 같은 값처럼 다루지 않는 것이 중요했습니다.

인증 결과는 성공/실패가 아니라 상태로 남겨야 했습니다

쿠키 책임을 옮긴 뒤에는 인증 판단 자체를 정리했습니다. Gateway는 더 이상 단순히 요청을 넘기는 reverse proxy가 아니라, 인증된 컨텍스트를 만들어 Backend로 전달하는 레이어가 됩니다.

Gateway 쪽에서 더 신경 쓴 부분은 토큰 검증 자체보다 검증 결과를 상태로 남기는 일이었습니다. 토큰 검증 결과를 성공과 실패 두 가지로만 남기면 정책은 다시 Backend 안쪽으로 퍼집니다. 만료된 인증 정보인지, 형식이 잘못된 인증 정보인지, 아예 없는 요청인지에 따라 이후 처리가 달라지기 때문입니다.

그래서 Gateway는 인증 결과를 다음처럼 구분된 상태로 전달하도록 했습니다.

상태의미Backend에서의 처리 기준
validGateway가 인증 가능한 사용자 컨텍스트를 만들 수 있음내부 헤더를 기반으로 principal 복원
expired인증 정보는 있었지만 만료됨경로에 따라 refresh 유도 또는 만료 응답
invalid형식이 잘못됐거나 신뢰할 수 없는 인증 정보refresh 대상으로 보지 않고 인증 실패 처리
missing인증 정보가 없음공개 경로 여부에 따라 통과 또는 인증 요구

예를 들어 expiredinvalid와 운영 의미가 다릅니다. expired는 refresh 흐름으로 이어질 수 있지만, invalid는 그렇지 않을 수 있습니다. 상태를 나누면 Gateway는 판단을 끝내고, Backend는 그 상태를 바탕으로 경로별 정책만 적용할 수 있습니다.

이 구조가 복잡도를 없애지는 않습니다. 복잡도는 Gateway 쪽으로 옮겨옵니다. 대신 실패가 더 앞단에서 보이고, 계약이 깨졌을 때 어느 경계에서 문제가 생겼는지 좁혀보기 쉬워집니다. 인증 정책이 여러 레이어에 퍼져 조용히 어긋나는 것보다, Gateway 경계에서 실패가 드러나는 편이 운영에서는 더 다루기 쉽다고 판단했습니다.

토큰과 Redis 세션이 동시에 있을 때는 세션을 기준으로 봤습니다

세션 기반 인증을 추가하면 애매한 구간이 생깁니다. 요청 안에는 기존 토큰이 남아 있고, 동시에 서버 세션도 존재할 수 있습니다. 이때 둘을 같은 수준의 근거로 보면 문제가 다시 복잡해집니다.

Gateway의 판단 기준은 요청이 들고 온 토큰 값보다 서버가 관리하는 세션 상태를 우선한다는 쪽으로 잡았습니다. 사용자의 권한이 바뀌었는데 브라우저가 오래된 토큰을 들고 있다면, 토큰만 보는 구조에서는 이전 권한으로 판단할 여지가 남습니다. 반대로 Redis 세션에 저장된 권한 상태를 Gateway의 기준으로 삼으면, 권한 변경 이후의 요청도 같은 기준으로 처리할 수 있습니다.

물론 이 방식도 비용이 있습니다. 매 요청마다 세션 저장소를 조회해야 하고, 마지막 접근 시각을 갱신하는 write가 과도하게 발생하지 않도록 제한해야 합니다. 그래서 read와 write를 분리해서 봤습니다.

  • read는 인증 판단에 필요한 비용입니다.
  • write는 세션 유지 정책을 위한 비용입니다.

세션 조회는 매 요청 수행하되, 마지막 접근 시각 갱신은 10분 단위로 묶었습니다. 이렇게 하면 요청 수와 Redis write가 1:1로 증가하지 않습니다. 다만 이 최적화는 읽기 경로를 줄인 것이 아니라 touch write만 제한한 것입니다. 세션 판단 자체는 매 요청 Redis 상태를 기준으로 하고, 갱신 실패나 저장소 지연은 인증 실패와 분리해서 로그로 볼 수 있어야 했습니다. 세션 기반 인증의 장점을 유지하면서도 저장소 부하를 일정 수준으로 제한하기 위한 선택이었습니다.

세션 저장소를 인증 DB처럼 쓰지 않기 위해 제한을 뒀습니다

Redis 세션을 기준으로 인증 상태를 판단한다고 해서 Redis에 모든 인증 정보를 밀어 넣는 것은 피했습니다. 세션 저장소가 커질수록 요청마다 읽는 데이터가 무거워지고, 장애가 났을 때 어느 값이 원본인지도 흐려질 수 있습니다. 그래서 세션에는 Gateway가 다음 요청을 판단하는 데 필요한 최소한의 사용자 식별 정보와 권한 상태만 남기는 쪽으로 선을 그었습니다.

세션 저장소를 다룰 때는 세 가지를 제한했습니다.

  • 세션 값은 다음 요청의 인증 판단에 필요한 정보까지만 담습니다.
  • 마지막 접근 시각 갱신은 매 요청마다 하지 않고, 일정 간격으로 묶어 write 부하를 제한합니다.
  • 권한 변경이나 강제 로그아웃처럼 즉시 반영되어야 하는 상태는 토큰 만료를 기다리지 않고 세션 삭제 또는 세션 값 갱신으로 반영합니다.

이 구분이 없으면 세션 기반 인증은 금방 애매해집니다. 토큰보다 서버 세션을 우선한다고 말하면서도, 실제로는 오래된 세션 payload를 계속 믿게 될 수 있습니다. 반대로 모든 요청마다 세션을 과하게 갱신하면 인증 레이어가 Redis write 부하를 만드는 지점이 됩니다. 그래서 세션 조회는 인증 판단의 일부로 받아들이되, 세션 유지 정책은 별도의 비용으로 보고 제한했습니다.

확인한 지점그냥 두면 생기는 문제정리한 방향
세션 payload요청마다 읽는 데이터가 커지고 원본 데이터와의 경계가 흐려짐Gateway 판단에 필요한 식별 정보와 권한 상태 중심으로 제한
마지막 접근 시각요청 수만큼 Redis write가 증가read와 write를 분리하고 갱신 주기를 묶음
권한 변경토큰에는 새 권한이 있어도 세션에는 이전 권한이 남을 수 있음refresh 또는 권한 변경 이후 세션 값을 함께 갱신
강제 로그아웃발급된 토큰이 만료될 때까지 접근이 남을 수 있음세션 삭제를 다음 요청의 인증 판단에 반영

이 부분은 구현보다 운영에서 더 중요했습니다. 장애가 났을 때 “Redis가 느려졌다”로 끝나면 원인을 좁히기 어렵습니다. 세션 조회 지연인지, 세션 write 증가인지, 권한 갱신 누락인지, 강제 로그아웃 반영 실패인지가 각각 다른 문제이기 때문입니다. Gateway가 인증 상태의 최종 판단자가 된다면, 세션 저장소를 보는 방식도 함께 관찰 가능해야 했습니다.

refresh 이후 401은 토큰 문제가 아니라 세션 상태 문제였습니다

refresh 이후 세션 권한 갱신 흐름
refresh 이후 세션 권한 갱신 흐름

전환 과정에서 가장 선명하게 드러난 문제는 refresh 이후 권한 불일치였습니다. 겉으로는 단순한 401처럼 보였지만, 원인은 토큰 발급 실패가 아니었습니다.

refresh 후 새 토큰에는 갱신된 권한이 들어 있었지만, Redis 세션에는 이전 권한 상태가 남아 있었습니다. Gateway는 토큰보다 Redis 세션을 기준으로 내부 서비스에 권한 정보를 전달하고 있었기 때문에, 새 토큰을 받은 뒤에도 이어지는 요청에서 이전 권한이 사용됐습니다. 결과적으로 권한이 필요한 API에서 401이 재현됐습니다.

원인 확인은 토큰 발급 성공 여부에서 멈추지 않았습니다. 같은 세션으로 refresh 요청 → 권한이 필요한 API 호출 순서를 고정하고, refresh 이후 Redis 세션 저장값, Gateway가 만든 내부 인증 헤더, 이어지는 권한 API 응답을 같은 흐름으로 따라갔습니다. 수정 전에는 401이 재현됐고, 세션 갱신 순서를 조정한 뒤에는 200 응답으로 확인했습니다.

수정 방향은 단순했습니다. 세션 식별자는 유지하되, 세션 내부의 사용자 상태와 권한 정보는 최신 인증 정보 기준으로 다시 기록하도록 했습니다. 처리 순서도 사용자 상태 계산 → Redis 세션 갱신 → 응답 쿠키와 내부 전달값 생성 순서로 고정했습니다. 200 응답만 확인하지 않고, 이후 요청에서 Gateway가 내부 서비스로 전달하는 권한 상태까지 회귀 테스트로 묶었습니다.

동시 요청에서는 “마지막으로 도착한 요청”보다 “최종 상태”가 중요했습니다

refresh 문제를 고친 뒤에도 한 가지를 더 확인해야 했습니다. 브라우저에서는 요청이 항상 순서대로 끝나지 않습니다. 사용자가 화면을 이동하거나 여러 API가 동시에 호출되면, refresh 요청과 일반 API 요청이 거의 같은 시간에 Gateway를 통과할 수 있습니다. 이때 단순히 “먼저 온 요청을 먼저 처리한다”는 기준만으로는 충분하지 않습니다.

refresh와 병렬 요청의 세션 상태 전이
refresh와 병렬 요청의 세션 상태 전이

문제가 되는 흐름은 대략 다음 순서로 재현될 수 있습니다.

  1. 기존 세션에는 이전 권한 상태가 남아 있습니다.
  2. refresh 요청이 들어와 새 권한을 계산합니다.
  3. 거의 동시에 일반 API 요청이 들어와 기존 세션 값을 읽습니다.
  4. refresh는 성공했지만, 일반 API는 이전 권한 기준으로 판단될 수 있습니다.

이 경우 사용자는 “방금 갱신됐는데 왜 401이 나오지?”라고 느끼게 됩니다. 서버 입장에서는 각 요청이 개별적으로는 정상 처리됐더라도, 사용자 경험에서는 인증 상태가 흔들린 것처럼 보입니다.

그래서 회귀 테스트는 단일 요청의 성공 여부보다 같은 세션에서 상태가 어떤 순서로 바뀌는지를 기준으로 잡았습니다. refresh 이후에는 새 토큰, Redis 세션 값, Gateway가 내부 서비스로 전달하는 권한 정보가 같은 기준을 바라봐야 합니다. 또한 로그아웃이나 계정 차단처럼 세션을 무효화하는 흐름에서는 늦게 도착한 요청이 이전 세션 상태를 계속 믿지 않는지도 확인해야 했습니다.

경계 조건놓치면 생기는 현상확인한 기준
refresh 직후 병렬 요청새 토큰을 받았는데 이어지는 API가 이전 권한으로 401 처리됨refresh 이후 세션 값과 내부 전달 권한이 함께 갱신되는지 확인
로그아웃 직후 요청브라우저에서는 로그아웃됐지만 늦게 도착한 요청이 기존 인증으로 통과할 수 있음세션 삭제 이후 다음 요청이 인증 실패로 판단되는지 확인
외부 내부 헤더 주입클라이언트가 만든 헤더와 Gateway가 만든 헤더를 구분하기 어려움외부 요청의 내부 인증 헤더를 제거하고 Gateway가 다시 주입하는지 확인
기존 토큰 경로와 세션 경로 공존전환 중 같은 요청이 서로 다른 인증 경로를 탈 수 있음세션 쿠키 유무를 기준으로 경로를 나누고 로그에서 구분

이 경계 조건은 기능 목록에는 잘 드러나지 않습니다. 인증 전환에서 실제 문제가 되는 지점은 대개 요청 사이에 있습니다. 로그인, refresh, 권한 API를 각각 성공시키는 것보다 같은 세션이 여러 요청을 거치며 어떤 상태로 이동하는지 보는 쪽이 더 안전했습니다.

회귀 테스트는 요청 하나가 아니라 상태 전이를 따라가야 했습니다

이 전환에서 단순히 로그인 성공이나 200 응답만 확인하는 것은 충분하지 않았습니다. 인증 전환 문제는 대부분 “로그인은 됐는데 이후 요청에서 권한이 다르게 보이는” 형태로 나타나기 때문입니다. 그래서 테스트 기준도 요청 하나의 성공 여부가 아니라, 같은 세션이 여러 요청을 거치며 어떤 상태로 바뀌는지를 확인하는 쪽으로 잡았습니다.

대표적으로 확인한 흐름은 다섯 가지입니다.

  • 세션이 없는 요청은 기존 토큰 검증 경로로 처리됩니다.
  • 세션이 있는 요청은 Redis 세션 값을 기준으로 인증 상태를 계산합니다.
  • refresh 이후에는 새 토큰의 권한 정보와 Redis 세션의 권한 정보가 같은 기준으로 갱신되어야 합니다.
  • 외부에서 들어온 내부 인증 헤더는 제거되고, Gateway가 계산한 값만 내부 서비스에 전달됩니다.
  • 로그아웃이나 계정 차단 이후에는 세션 삭제 또는 무효화가 다음 요청의 인증 판단에 반영되어야 합니다.

이 흐름을 분리해두지 않으면 배포 후 문제가 생겼을 때 원인을 좁히기 어렵습니다. 토큰 발급 문제인지, 쿠키 전달 문제인지, Redis 세션 갱신 문제인지, 내부 헤더 생성 문제인지가 모두 비슷한 401로 보이기 때문입니다.

운영 로그도 같은 기준으로 남겼습니다. 단순히 401 응답만 세는 대신, 세션 조회 결과, 권한 상태 전이, 기존 토큰 경로 사용 여부를 구분했습니다. 그러면 “왜 401이 늘었나?”라는 질문을 “어떤 인증 경로에서 401이 늘었나?”로 바꿀 수 있습니다. 전환 직후에는 이 구분이 문제를 좁히는 출발점이 됩니다.

운영에서 실제로 달라진 확인 순서

전환 이후 인증 문제를 확인하는 순서가 달라졌습니다. 예전에는 401이 발생하면 토큰 발급, 쿠키 전달, Backend 필터, 컨트롤러 권한 체크를 함께 봐야 했습니다. 이제는 먼저 Gateway 로그에서 어떤 인증 경로를 탔는지 확인합니다.

확인 순서는 대략 다음처럼 좁혀졌습니다.

  1. 요청에 세션 쿠키가 있었는지 확인합니다.
  2. Gateway가 Redis 세션을 조회했는지 확인합니다.
  3. 세션 조회 결과가 valid, expired, invalid, missing 중 어디에 가까웠는지 확인합니다.
  4. Gateway가 내부 인증 헤더를 새로 구성했는지 확인합니다.
  5. Backend의 권한 실패가 도메인 권한 문제인지, 인증 상태 전달 문제인지 분리합니다.

이 순서가 생기면 401을 보는 방식이 달라집니다. 모든 인증 실패를 같은 실패로 묶지 않고, 세션 경로의 실패인지, 기존 토큰 경로의 실패인지, 내부 계약 전달 실패인지를 먼저 나눌 수 있습니다. 전환 작업에서 운영 로그를 먼저 정리한 이유도 여기에 있습니다.

Backend 쪽 변화도 단순한 코드 정리보다 운영 의미가 컸습니다. Backend는 토큰과 쿠키를 직접 해석하는 대신, Gateway가 만든 인증 상태를 소비합니다. 덕분에 도메인 로직은 브라우저 쿠키 정책이나 refresh 세부 흐름에서 한 걸음 떨어질 수 있었습니다. 반대로 Gateway는 더 중요한 장애 지점이 됐고, 세션 저장소 지연이나 내부 헤더 생성 오류를 더 엄격하게 관찰해야 하는 레이어가 됐습니다.

이 설계가 남기는 트레이드오프

Gateway로 인증 판단을 옮기면 Backend의 인증 코드는 단순해지지만, Gateway는 그만큼 더 민감한 장애 지점이 됩니다. Redis 연결 문제, 세션 조회 지연, 내부 헤더 생성 오류가 모두 사용자 요청의 앞단에서 드러납니다. 그래서 이 전환은 인증 코드를 한곳으로 모으는 작업이면서 동시에, Gateway 로그와 세션 저장소 상태를 더 먼저 보게 만드는 운영 방식의 변화이기도 했습니다.

모든 서비스가 이런 구조를 선택할 필요는 없습니다. 단일 클라이언트와 단순한 토큰 검증만 가진 서비스라면 Backend 내부 검증이 더 작고 명확할 수 있습니다. 반대로 여러 클라이언트, 브라우저 쿠키, refresh, 강제 로그아웃, 권한 갱신, 내부 서비스 호출이 함께 있다면 인증 판단 위치가 흩어지는 순간 문제 재현이 어려워집니다. 이 글의 맥락에서는 그 비용이 Gateway에 책임을 모으는 비용보다 컸습니다.

이 전환에서 의도적으로 받아들인 트레이드오프는 다음과 같습니다.

선택얻은 것새로 생긴 부담
Gateway가 인증 상태 판단인증 정책 변경 지점이 명확해짐Gateway 장애 영향이 커짐
Redis 세션 우선강제 로그아웃과 권한 갱신 반영이 쉬워짐세션 저장소 read 비용이 생김
내부 헤더 계약 사용Backend는 인증 결과만 소비 가능헤더 sanitize와 계약 테스트가 중요해짐

인증을 다룰 때 끝까지 붙잡아야 했던 질문은 토큰 종류보다 “어느 레이어가 어떤 값을 최종 신뢰하는가”였습니다. 이번 전환은 JWT를 Redis 세션으로 바꾼 작업에 그치지 않았습니다. 브라우저와 Gateway 사이의 계약, Gateway와 내부 서비스 사이의 계약을 분리하고, 401이 발생했을 때 세션 조회 실패인지 기존 토큰 경로 문제인지 내부 헤더 전달 문제인지 추적할 수 있게 만든 작업이었습니다.