← 글 목록

LearningCollector 포스팅이 갑자기 멈춘 이유: 401 에러부터 세션 인증까지

/ 8분 분량

블로그에 자동으로 올라가던 학습 글들이 며칠째 안 보여서 이상하다 싶었는데, 원인을 추적해보니 보안 패치 하나가 파이프라인 전체를 조용히 막아버린 거였다. 로그를 뒤져서 원인을 찾고, 임시방편이 아니라 제대로 된 세션 인증 방식으로 고친 과정을 정리한다.

학습 주제

  • 주제: 자동 포스팅 파이프라인(LearningCollector)의 401 인증 실패 원인 분석 및 세션 기반 인증 구현
  • 날짜: 2026-08-08

탐구 과정

며칠 전부터 블로그에 새 글이 안 올라오고 있다는 걸 알아챘다. 처음엔 "API 문제인가?" 정도로 막연하게 의심만 하고 있었는데, 직접 로그 파일(cron.log, promtail_feed.jsonl)을 열어보니 확실한 증거가 있었다.

{"event":"api_call","api":"blog","method":"POST","endpoint":"/api/posts","status_code":401,"success":false,"error":"{\"error\":\"Unauthorized\"}"}

시간대별로 로그를 훑어보니 특정 시점 전후로 상태 코드가 확 바뀌어 있었다. 그 이전엔 실패해도 409(중복 방지, 정상 동작)였는데, 어느 순간부터 전부 401로 죽어있었다. 이 타이밍을 커밋 로그와 맞춰보니 원인이 명확해졌다.

6838e1f Harden auth/visitor-log against IP-spoofing and add scan mitigations
- Restrict autoAuth's localhost bypass to development only.

내가 짠 보안 강화 커밋이 범인이었다. localhost 요청이면 로그인 없이 자동으로 관리자 세션을 부여하던 autoAuth 로직을, NODE_ENV=development에서만 동작하도록 제한한 거였다. 이유는 명확했다 — X-Forwarded-For 헤더를 127.0.0.1로 스푸핑하면 외부에서도 이 우회를 뚫고 관리자 권한을 얻을 수 있는 진짜 취약점이 있었기 때문이다.

문제는 LearningCollector가 이 우회에 그냥 얹혀서 로그인 절차 없이 POST /api/posts만 날리도록 짜여 있었다는 것. 백엔드가 production 모드로 재시작되는 순간, 로그인 안 한 요청은 전부 막히게 된 거다.

핵심 학습 내용

원인과 해결 방향을 분리해서 생각하기

가장 먼저 든 유혹은 "그냥 autoAuth 우회를 다시 풀어버릴까"였는데, 이건 원래 막았던 취약점을 그대로 재도입하는 셈이라 선택지에서 뺐다. 대신 정공법을 택했다:

  1. POST /api/auth/login으로 실제 로그인해서 세션 쿠키(connect.sid)를 받는다
  2. 이후 /api/posts 요청에 이 쿠키를 실어 보낸다
  3. 401이 뜨면 세션이 만료된 걸로 보고 재로그인 후 한 번 재시도한다

이렇게 blog_api.py를 고치고, 비밀번호는 .env의 BLOG_ADMIN_PASSWORD로 분리해서 관리하게 했다.

날짜가 뭉치는 문제도 같이 해결

인증만 고치면 밀린 초안들을 몰아서 올릴 때 전부 "오늘 날짜"로 찍힐 게 뻔했다. 이건 원치 않는 결과였다. 다행히 draft 파일명에 이미 실제 작업 시각(커밋시간/병합일/exported시간)이 박혀 있었기 때문에, 이 값을 읽어서 포스팅 API에 date 필드로 같이 넘기도록 컨트롤러/서비스 단까지 고쳤다.

# blog_api.py 핵심 흐름
session = login(admin_password)  # /api/auth/login
response = post(url, cookies=session, json={
    "title": title,
    "content": content,
    "date": extracted_date_from_filename,  # 핵심
})
if response.status_code == 401:
    session = login(admin_password)  # 재로그인 후 1회 재시도
    response = post(...)

테스트 글을 하나 만들어서 date="2020-01-01"로 지정해봤더니 실제로 그 날짜로 저장되는 걸 확인했고, 바로 삭제해서 블로그엔 흔적을 남기지 않았다.

백로그 정리는 예상보다 복잡했다

이제 밀린 초안을 처리할 차례였는데, 여기서 몇 번 더 예상이 빗나갔다.

  • 파일 수정시각 기준으로 "242개 밀렸다"고 생각했는데, 실제로 블로그 DB와 대조해보니 dev 163개, pr 61개, study 18개로 구성돼 있었고 이미 포스팅된 것도 섞여 있어서 진짜 백로그는 88개뿐이었다.
  • dev 163개는 알고 보니 8/3에 이미 "auto 모드에서는 dev 커밋 단위 포스팅을 중단한다"는 정책 변경이 있었던 이후 생성만 되고 절대 포스팅 안 되는 죽은 파일들이었다. 확인해보니 실제로는 538개나 있었다 — 근사치를 믿으면 안 된다는 걸 다시 느꼈다.
  • 8월분 23개는 원래 날짜로 바로 포스팅했고, 2~7월분 56개는 제목 유사도로 26개가 "이미 포스팅된 글과 중복 아닐까" 걸러졌다.

제목만 보고 판단하면 안 된다

여기서 배운 게 컸다. 제목이 비슷하다고 무조건 중복은 아니고, 반대로 제목이 달라도 내용이 겹칠 수 있다. 그래서 raw 문자열 유사도 대신 TF-IDF로 본문 자체를 비교했다.

# 마크다운 템플릿 때문에 raw 유사도는 오탐이 심함
# → 기술 용어에 가중치를 준 TF-IDF로 재검증

그 결과 제목만으로 "안 겹친다"고 판단했던 16개 중 실제로 BOJ 1904 DP 글은 유사도 0.91로 확실한 중복이었고(같은 백준 1904번을 다룬 다른 초안이었다), ArgoCD 관련 글 하나는 0.49로 애매하게 걸렸다. 나머지 14개는 0.05~0.22 수준이라 진짜 겹치지 않는 새 글로 확정했다.

이해한 내용

  • 보안 강화와 자동화 파이프라인은 항상 같이 검토해야 한다. 취약점을 막는 게 옳은 방향이었지만, 그 우회에 의존하던 다른 시스템(LearningCollector)이 있다는 걸 놓치면 조용히 다른 게 고장 난다.
  • 세션 기반 인증은 "로그인 → 쿠키 유지 → 만료 시 재로그인"이라는 뻔한 패턴이지만, 이 패턴을 API 클라이언트 쪽에 정확히 구현하는 게 생각보다 손이 많이 갔다. 401을 그냥 실패로 처리하지 않고 "세션 만료일 수 있으니 한 번 더 시도"로 다루는 재시도 로직이 핵심이었다.
  • 근사치로 낸 숫자(242개, 163개, 65개)는 계속 틀렸다. 파일 수정시각 기준 추정, DB 직접 대조, 제목 유사도, 본문 TF-IDF — 검증 방법을 바꿀 때마다 숫자가 달라졌다. 정확한 판단이 필요한 작업일수록 근사치에 안주하지 말고 실제 데이터(DB, 본문)를 직접 대조해야 한다는 걸 체감했다.
  • 문자열 유사도와 의미적 유사도는 다르다. 마크다운 포맷 때문에 같은 템플릿을 쓰는 글들은 raw 텍스트로 비교하면 실제로 안 겹쳐도 유사도가 높게 나올 수 있고, 반대로 기술 용어 위주로 가중치를 주면 진짜 겹치는 내용을 더 정확히 잡아낼 수 있었다.

실전 적용

  • 앞으로 인증/보안 관련 코드를 고칠 때는 그 우회 로직에 의존하는 다른 스크립트나 크론잡이 있는지 먼저 grep으로 확인하는 습관을 들여야겠다.
  • API 클라이언트를 짤 때 "실패하면 그냥 에러" 대신 "401이면 재인증 후 1회 재시도"라는 패턴을 템플릿처럼 만들어두면 다른 프로젝트에도 재사용할 수 있을 것 같다.
  • 대량 데이터(초안 파일들) 중복 판별이 필요할 때 제목 유사도로 1차 필터링하고 TF-IDF 본문 비교로 2차 검증하는 2단계 프로세스가 꽤 유용했다 — 나중에 비슷한 정리 작업(예: 메모, 노트 중복 제거)에도 써먹을 수 있을 듯하다.

추가 학습 계획

  • 세션 쿠키 대신 JWT 기반 인증으로 바꾸면 재로그인 로직이 더 단순해질지 비교해보고 싶다.
  • TF-IDF 말고 임베딩 기반 유사도(예: 문장 임베딩 코사인 유사도)로 중복 판별을 다시 해보면 결과가 얼마나 달라지는지 실험해보고 싶다.
  • IP 스푸핑을 이용한 인증 우회 공격의 다른 사례들을 더 찾아보고, X-Forwarded-For 헤더를 신뢰 경계에서 어떻게 다뤄야 하는지 체계적으로 정리해볼 계획이다.