inblog logo
|
찬찬잉
    Front-end Developer

    오만함을 부순 GS 인증 보안 리팩토링

    솔직히 고백하자면, 어느 순간부터 프론트엔드 영역에서 "더 이상 큼직하게 성장할 만한 기술적 챌린지가 있을까?"라는 생각을 했었다.
    찬찬잉's avatar
    찬찬잉
    Aug 05, 2026
    오만함을 부순 GS 인증 보안 리팩토링
    Contents
    소명서로 덮어뒀던 과거의 시한폭탄발상의 전환: 감출 수 없다면 아예 없애버리자SSR이 가진 진정한 장점의 발견마주친 허들, 그리고 돌파회고를 마치며

    "프론트엔드 개발자로서 더 성장할 게 있을까?" 오만을 부순 GS 인증 보안 리팩토링

    솔직히 고백하자면, 어느 순간부터 프론트엔드 영역에서 "더 이상 큼직하게 성장할 만한 기술적 챌린지가 있을까?"라는 생각을 했었다.

    UI를 그리고, 상태를 관리하고, API를 연동하는 익숙한 궤도에 오르면서 생긴 안일함이었다…

    하지만 이번 GS(Good Software) 인증 보안 결함 대응 과정을 거치며, 그것이 완전한 오만이고 착각이었음을 뼈저리게 깨달았다…

    소명서로 덮어뒀던 과거의 시한폭탄

    과거 GS 인증 심사 때도 비슷한 지적이 있었다.

    "크로스 사이트 스크립트(XSS) 공격 시 쿠키 접근 방지를 위한 HttpOnly 쿠키가 설정되어 있지 않음"

    당시에는 이 문제를 기술적으로 완벽히 해결하기보다, "현재 프론트엔드 구조상 Authorization 헤더에 토큰을 담아 보내려면 JavaScript가 토큰을 읽어야 하므로 HttpOnly를 걸 수 없다"는 내용의 소명서를 제출해 아슬아슬하게 위기를 넘겼었다. 작동하는 코드를 뒤엎는 것이 두려웠고, 프론트엔드에서는 어쩔 수 없는 한계라고 스스로 타협했던 것이다.

    하지만 이번엔 달랐다. 또다시 소명서 뒤에 숨어 결과를 흐지부지 만들고 싶지 않았다. 변명이 아니라, 시스템의 근본적인 취약점을 도려내는 진짜 '엔지니어링'을 증명하고 싶었다.

    발상의 전환: 감출 수 없다면 아예 없애버리자

    가장 먼저 검토한 "그냥 accessToken에 HttpOnly를 붙이는 방법"은 역시나 불가능했다. 프론트엔드에서 API 요청 시 헤더를 구성할 방법이 원천 차단되기 때문이다.

    여기서 발상을 바꿨다. "JavaScript가 읽을 수 없게 만드는 것이 불가능하다면, 아예 쿠키에 두지 않으면 되지 않을까?"

    목표를 전면 수정했다.

    1. accessToken: 쿠키에서 완전 삭제. 오직 메모리(Zustand)에만 보관한다.

    2. userRole: 쿠키에서 완전 삭제. 권한은 메모리에 보관하되, 빈 경우 JWT의 auth 클레임에서 파생시켜 사용한다.

    3. refreshToken: 기존대로 HttpOnly 쿠키 유지 (JavaScript 접근 불가).

    결과적으로, 브라우저 프로그램 상(document.cookie)에서 접근할 수 있는 인증 정보 자체를 0개로 만들어버렸다.

    SSR이 가진 진정한 장점의 발견

    이 구조를 설계하고 구현하면서, 내가 사용 중인 SSR(Server-Side Rendering) 환경의 진가를 다시 한번 체감했다.

    단순히 클라이언트(CSR)에서만 동작하는 앱이었다면, 새로고침 시 토큰이 날아가는 문제를 해결하기 위해 꽤나 억지스러운 로직이 필요했을 것이다. 하지만 SSR 아키텍처에서는 보안과 사용성을 동시에 챙길 수 있는 강력한 무기가 있었다.

    1. 서버와 클라이언트의 역할 분리
      브라우저(클라이언트)는 XSS에 취약한 텍스트 저장소(localStorage, 일반 쿠키)를 일절 쓰지 않는다. 오직 수명이 15분짜리인 휘발성 토큰만 메모리에 들고 API를 찌른다.

    2. HttpOnly 쿠키의 안전한 릴레이
      사용자의 진짜 장기 자격증명인 refreshToken은 오직 HttpOnly 쿠키로만 존재한다. 브라우저는 이 값을 절대 읽을 수 없지만, 네트워크를 타고 백엔드(또는 SSR 서버)로 갈 때는 브라우저가 알아서 안전하게 동봉한다.

    3. 미들웨어와 세션의 우아한 통제
      새로고침을 하거나 페이지를 이동할 때, SSR 서버(미들웨어)는 클라이언트의 메모리가 비어있든 말든 상관없이 전달받은 HttpOnly 토큰을 검증해 인가 처리를 할 수 있다. 즉, 클라이언트의 보안은 극대화하면서도 서버 사이드에서 매끄럽게 세션을 복구하고 유지하는 것. 이것이야말로 SSR을 단순한 SEO 도구가 아니라, 견고한 보안 아키텍처로 활용하는 진짜 장점이었다.

    마주친 허들, 그리고 돌파

    물론 토큰을 메모리로 옮기는 과정은 평탄하지 않았다. "F5(새로고침)를 누르면 로그아웃되는 현상"부터 "화면 진입 직후 API가 401 에러를 뱉는 레이스 컨디션"까지 수많은 엣지 케이스가 터져 나왔다.

    이를 해결하기 위해 꽤 많은 공을 들였다.

    • Silent Refresh 파이프라인 구축: 토큰이 없으면 즉각 로그아웃 시키는 대신, 비동기로 재발급(ensureSessionRestored)을 먼저 시도하도록 인터셉터를 개조했다.

    • 요청 홀딩(Holding): 토큰 복구가 끝나기 전에 나가는 API 요청들은 실패하지 않도록 Promise 큐에 대기시켰다가 복구 완료 후 한 번에 쏘도록 처리했다.

    • 레거시 청소: 예전 버전의 쿠키가 남아 보안 스캐너에 오탐지되지 않도록, 앱 기동 시 잔재(LEGACY_COOKIE_NAMES)를 깔끔하게 지우는 로직도 추가했다.

    회고를 마치며

    과거의 나는 프론트엔드 보안을 '백엔드가 해주는 것', 혹은 '어느 정도 타협해야 하는 것'으로 여겼다.

    하지만 이번 리팩토링을 통해 클라이언트 레벨에서도 장기 자격증명(refreshToken)을 스크립트로부터 완전히 격리하고, CSRF 표면을 줄이며, 권한 조작 우회를 원천 차단하는 견고한 구조를 직접 짜냈다.

    "더 성장할 거리가 있을까?"라는 질문은 오만이었다. 껍데기가 아닌 시스템의 근간을 다루는 영역에서, 프론트엔드 엔지니어로서 해결해야 할 깊이 있는 숙제는 여전히 무궁무진하다. 이번 GS 인증 대응은 단순한 '통과'가 아니라, 스스로의 한계를 부수고 한 단계 더 나아간 경험이었다…

    Share article
    Contents
    소명서로 덮어뒀던 과거의 시한폭탄발상의 전환: 감출 수 없다면 아예 없애버리자SSR이 가진 진정한 장점의 발견마주친 허들, 그리고 돌파회고를 마치며

    찬찬잉

    RSS·Powered by Inblog