Skip to content

feat(S15P11A705-63): Google 소셜 로그인·로그아웃과 JWT 쿠키 인증 도입 #32

Description

@cherry-go-round

배경 / 목적

현재 백엔드에 인증이 없습니다. foundation reset에서 Spring Security와 SecurityConfig를 의도적으로 제거했고, 개인의 장소 기록·위치 데이터를 다루는 서비스라 본인 식별과 보호 경로가 필요합니다.

BFF와 리소스 서버를 한 서버로 운영하기로 확정했고, IETF draft-ietf-oauth-browser-based-apps가 개인 데이터를 다루는 애플리케이션에 BFF를 "strongly recommended"로 두면서 브라우저 쿠키에 Secure·HttpOnly를 MUST로 요구하는 데 따라 토큰을 JS에 노출하지 않는 쿠키 방식을 택했습니다.

인증 도입과 로그인 기능은 같은 SecurityConfig를 수정하고 JWT 서명키·클레임 규약을 공유하므로 분리할 수 없어 한 티켓으로 묶습니다. 다만 provider 3사를 한 번에 넣으면 과대해지므로 Google 하나로 골격을 완성하고 나머지는 후속 티켓으로 갑니다.

Jira (필수)

상위/관련 GitHub Issue

상세 내용

설계 근거와 클라이언트 계약은 11_인증_설계, 엔드포인트 계약은 08_API_명세 §1·§3에 있습니다.

확정된 값

항목
토큰 Access 30분 / Refresh 7일, JWT
전달 HttpOnly + Secure + SameSite=Lax 쿠키
Refresh 쿠키 Path /api/core/v1/auth (재발급·로그아웃 모두 포함, 일반 API 제외)
Refresh 저장 Redis, 회전 발급
CSRF XSRF-TOKEN 쿠키 → X-XSRF-TOKEN 헤더, 실패 403
로그인 표시 logged_in=1 비-HttpOnly 쿠키
콜백 복귀 /auth/callback (실패 시 ?error=OAUTH_FAILED)
CORS 운영 불필요(same-origin). 로컬만 필요

범위

  • Spring Security + spring-boot-starter-security-oauth2-client 의존성 (Boot 4에서 starter 이름이 바뀌었습니다)
  • social_account 마이그레이션core.member는 이미 있습니다(feat(S15P11A705-41): member 엔티티 + BaseEntity(created_at·soft delete) (도메인 스캐폴딩 3/3) #25). 번호는 member·auth 구간인 V50
  • SocialAccount 엔티티 — BaseEntity 상속, @Table(schema = "core"), 부분 유니크 UNIQUE(provider, provider_user_id) WHERE deleted_at IS NULL
  • Google OAuth: GET /auth/google/login, GET /auth/google/callback (redirect URI에 context path /api/core 포함)
  • 콜백: 활성 social_account 있으면 로그인, 없으면 member + social_account 생성 후 로그인. 약관 동의는 프론트 전담
  • 콜백 응답은 302 + Set-Cookie. 본문 없음
  • POST /auth/refresh — 쿠키 기반, 204, 회전 발급
  • POST /auth/logout — Refresh 쿠키로 해당 세션만 무효화, 204
  • SecurityConfig — 공개 경로(actuator health·prometheus, 인증 엔드포인트, api-docs)와 보호 경로 구분
  • JWT 검증 필터 + principal 계약 — @LoginMember MemberPrincipal. 리졸버 본문만 교체하면 되도록 (member·인증 파트와 core 도메인의 접점 인터페이스 선합의 3건 #28 합의)
  • 인증 실패 401, CSRF 실패 403, 타인 자원 접근 404(리소스 은닉, 08 §1.2)
  • AuthenticationEntryPoint·AccessDeniedHandler직접 ApiResponse를 작성 — 필터 체인은 @RestControllerAdvice를 타지 않습니다
  • TraceIdFilter가 Security 필터 체인보다 먼저 실행되어야 traceIdnull이 되지 않습니다
  • 로컬 개발·테스트 인증 통과 방법 문서화 (인증을 통째로 우회하는 설정은 두지 않음)

이미 끝난 것 — 이 티켓 범위 아님

Jira 설명에는 남아 있지만 docs#12가 머지되어 완료된 항목입니다.

  • 08_API_명세 개정 (§1.1 쿠키 인증, §1.7 CSRF, §1.8 표시 쿠키, §2.1 /me/agreements 삭제, §3.2 콜백, §3.3 재발급, §3.4 로그아웃)
  • 프론트 도메인 구성 확인 → same-origin 확정, SameSite=Lax 확정

Jira 설명의 "OAuth state 쿠키는 Lax, 인증 쿠키는 Strict로 분리"도 폐기됐습니다. same-origin이라 전부 Lax 입니다.

할 일

  • Security·OAuth2 Client 의존성 추가 (Boot 4 starter 이름 확인)
  • V50__social_account.sql + SocialAccount 엔티티·리포지토리
  • MemberPrincipal + @LoginMember + LoginMemberArgumentResolver
  • Google OAuth 연동 (login·callback)
  • JWT 발급·검증, 쿠키 팩토리 (logged_in 포함)
  • Redis Refresh 저장소 + 회전 발급
  • /auth/refresh, /auth/logout
  • SecurityConfig + CSRF + EntryPoint·AccessDeniedHandler
  • ErrorCode.FORBIDDEN(403) 추가 — UNAUTHORIZED(401)가 아직 없으면 함께
  • api-conventions.mdv1 반영 (현재 예시가 @RequestMapping("/v1/...") 방식과 어긋남)
  • X-Debug-Member-Id 스텁이 머지되어 있으면 제거 — 조건부
  • 테스트 (Redis Testcontainers 지원 클래스 신설 필요)
  • authentication.md 갱신 + ADR 기록

완료 조건

  • Google 로그인 시작 요청이 Google 인가 페이지로 리다이렉트된다
  • 신규 Google 계정 콜백에서 membersocial_account가 생성되고 인증 쿠키가 발급된다
  • 기존 Google 계정 콜백에서 member가 추가 생성되지 않고 쿠키만 발급된다
  • 인증 쿠키에 HttpOnly·Secure·SameSite 속성이 모두 있고, logged_inHttpOnly가 아니다
  • Refresh 쿠키의 Path/api/core/v1/auth로 제한되어 일반 API 요청에 실리지 않는다
  • 유효한 Access 쿠키로 보호 경로 요청 시 2xx, 쿠키가 없거나 만료면 401
  • /actuator/health/actuator/prometheus는 인증 없이 접근된다
  • POST /auth/refresh204와 함께 새 쿠키를 발급하고, 회전 전 Refresh 재사용은 401
  • POST /auth/logout이 204를 반환하고, 이후 같은 Refresh로 재발급하면 401
  • Access가 만료된 상태에서도 로그아웃이 동작한다 (Refresh 쿠키로 식별)
  • CSRF 토큰 없는 변경 요청(POST·PUT·DELETE)이 403으로 차단된다
  • 401·403 응답이 공통 오류 봉투({success:false, error:{...}})를 따르고 traceIdnull이 아니다
  • V50__social_account.sql이 빈 PostgreSQL DB에서 성공한다
  • 소프트 삭제 후 동일 (provider, provider_user_id) 재INSERT가 성공한다 (부분 유니크)
  • 테스트가 PostgreSQL Testcontainers에서 수행되며 H2를 사용하지 않는다
  • ./gradlew clean check --no-daemon 통과
  • docs/development/authentication.md가 실제 구현과 일치한다
  • 인증 방식 결정과 검토한 대안이 ADR로 기록된다

Metadata

Metadata

Labels

enhancement새 기능·개선 (feat)

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions