Skip to content

❗[버그][Auth] 로그인 직후 401로 강제 로그아웃되는 버그 #36

Description

@EM-H20

🗒️ 설명

  • 소셜 로그인(Google/Firebase) 직후 홈 화면 진입 시 보호된 API 호출이 즉시 401을 반환하여 클라이언트가 강제 로그아웃 처리하는 현상이 발생합니다.
  • /api/auth/login은 정상 200, accessToken/refreshToken 발급도 정상이지만, 발급된 토큰으로 /api/todos 등 인증 필요 엔드포인트 호출 시 401 응답이 반환되며 응답 body가 비어 있습니다.
  • 클라이언트가 401을 토큰 만료로 간주해 reissue를 시도 → reissue도 200 성공 → 새 토큰으로 retry → 또 401 → 강제 로그아웃 → /login 으로 복귀합니다.
  • 결과적으로 어떤 사용자도 정상 로그인 직후 홈 진입을 유지할 수 없는 차단성 버그입니다.

🔄 재현 방법

  1. Flutter 앱(iOS Simulator)에서 Google 소셜 로그인 수행
  2. 백엔드 로그인 응답 200 수신 후 /home 진입
  3. /home 초기 진입 시 자동 호출되는 GET /api/todos가 401 반환
  4. 클라이언트 AuthInterceptor가 자동 reissue → 200 → retry → 다시 401
  5. 클라이언트 강제 로그아웃 실행, /login 으로 이동

🔍 원인 분석

  • 실제 JWT 검증을 담당하는 컴포넌트는 Spring MVC AuthInterceptor(HandlerInterceptor)입니다.
  • 그러나 SecurityConfig.filterChain.anyRequest().authenticated()로 인증을 요구하는 반면, SecurityFilterChain에는 SecurityContext를 채워주는 JWT 인증 필터가 등록되어 있지 않습니다.
  • 따라서 모든 보호 요청이 SecurityContext가 비어 있는 상태로 .authenticated() 검사에 실패하고, HttpStatusEntryPoint(UNAUTHORIZED)가 직접 401을 반환합니다 (그래서 응답 body가 비어 있음 — ControllerAdvice 미경유).
  • /api/auth/**AUTH_WHITELIST에 포함되어 permitAll 처리되므로, 로그인/리이슈는 통과하지만 그 외 모든 보호 엔드포인트가 차단됩니다.
  • 이는 토큰 발급 문제가 아니라 토큰 검증 자체가 일어나지 않는 구조 결함입니다.

📸 참고 자료

실제 클라이언트 로그 시퀀스:

POST /api/auth/login            → 200, accessToken length=146
GET  /api/todos                 → 401 (Response Text: null)
POST /api/auth/reissue          → 200, 새 토큰 발급 성공
GET  /api/todos (retry, 새 토큰) → 401
🚨 강제 로그아웃 실행

관련 파일:

  • SS-Web/src/main/java/com/elipair/spacestudyship/config/SecurityConfig.java — 인증 필터 누락 지점
  • SS-Web/src/main/java/com/elipair/spacestudyship/config/WebConfig.java — AuthInterceptor 등록 위치
  • SS-Auth/src/main/java/com/elipair/spacestudyship/auth/interceptor/AuthInterceptor.java — 실제 JWT 검증 로직
  • SS-Auth/src/main/java/com/elipair/spacestudyship/auth/constant/SecurityUrls.java — 화이트리스트 상수

✅ 예상 동작

  • 소셜 로그인 응답으로 받은 access token으로 /api/todos 등 보호 엔드포인트가 200으로 응답해야 합니다.
  • 잘못되거나 누락된 토큰으로 호출 시 401 + {"code":"UNAUTHENTICATED_REQUEST","message":"로그인이 필요합니다."} 형식의 JSON body가 반환되어야 합니다.
  • reissue는 access token 실제 만료 시점에만 발동하고, 정상 로그인 직후에는 발동하지 않아야 합니다.

⚙️ 환경 정보

  • OS: macOS (백엔드 Darwin 25.3.0, 클라이언트 iOS Simulator)
  • 백엔드: Spring Boot 4.0.2, Java 21, 멀티모듈 Gradle
  • 클라이언트: Flutter (iOS Simulator, type=IOS)
  • DB: PostgreSQL (Flyway 마이그레이션 v0.0.36)
  • 재현 빈도: 100% (소셜 로그인 시 항상 발생)

🙋‍♂️ 담당자

  • 백엔드: EM-H20
  • 프론트엔드: -
  • 디자인: -

Metadata

Metadata

Assignees

Labels

작업완료작업 완료 상태인 경우 (이슈 폐쇄)

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions