[feat] 소셜 로그인을 Firebase Authentication 기반으로 전환 - #10
Merged
Conversation
앱이 Firebase Auth로 로그인하기로 해, 서버 검증을 구글/애플 원본 토큰 검증에서
Firebase ID 토큰 검증으로 바꾼다.
- SocialTokenVerifier 인터페이스 + FirebaseTokenVerifier 구현: issuer
securetoken.google.com/{projectId}, aud {projectId} 를 검증하고, 로그인 수단은
firebase.sign_in_provider, 사용자 식별자는 sub(Firebase UID)에서 얻는다.
- FirebaseJwkSource: securetoken x509 인증서 엔드포인트에서 공개키를 가져와
캐싱한다(표준 JWKS 아님).
- API 단순화: 토큰이 provider를 자체 식별하므로 POST /api/auth/login/{provider}
→ POST /api/auth/login 단일 엔드포인트.
- 프로젝트 ID(gamss-cbdcb)는 비밀이 아니므로 application.yml 설정값으로 둔다.
GOOGLE_CLIENT_IDS·APPLE_CLIENT_IDS 시크릿과 기동 검증이 불필요해진다.
- 제거: OidcTokenVerifier, Google/AppleOAuthClient, OAuthClientResolver,
AllowedAudiences, OAuthProperties, OAuthClientIdsValidator, JwkSources 등
native OIDC 검증 레이어.
- 유지: 서비스 토큰 발급·재발급·해싱(JwtIssuer, TokenHasher), AuthService/
LoginService 흐름, ConflictRetry, SocialAccount(provider·providerId=UID).
close #9
Test Results102 tests - 14 102 ✅ - 14 58s ⏱️ +2s Results for commit f2afe5f. ± Comparison against base commit 110c9e5. This pull request removes 30 and adds 16 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Test Coverage
|
Firebase 전환으로 검증 방식이 raw OAuth 가 아니게 됐는데 패키지·클래스 이름이 그대로여서 정리한다. 동작 변화는 없다. - 패키지 com.nexters.gamss.auth.oauth → auth.social - OAuthProvider → SocialProvider - ErrorCode.UNSUPPORTED_OAUTH_PROVIDER → UNSUPPORTED_SOCIAL_PROVIDER
앱 개발자가 처리해야 할 에러와 공통 응답 포맷이 문서에 없어 보강한다. - ApiResponse·ErrorResponse 필드에 @Schema 설명 추가 (success/data/error 구조, error.code로 분기) - 로그인·재발급 엔드포인트 설명에 실패 응답 error.code 목록 명시 (INVALID_SOCIAL_TOKEN·WITHDRAWN_MEMBER·EXPIRED_TOKEN 등)
백틱 코드 스팬의 회색 배경이 줄마다 겹쳐 보이던 것을 마크다운 표로 바꿔 error.code·HTTP·설명을 정렬한다. 로그인·재발급·회원 6개 엔드포인트 통일.
kite707
reviewed
Jul 19, 2026
kite707
reviewed
Jul 19, 2026
외부 API 호출인데 타임아웃이 없어, 엔드포인트가 지연되면 스레드가 무한정 대기했다. refresh()가 @synchronized 라 하나가 멈추면 락을 기다리는 다른 로그인 스레드까지 함께 멈추므로, 연결 3초·요청 5초 타임아웃을 건다.
FirebaseJwkSource.get() 안의 HttpClient.send() 오류는 FirebaseTokenVerifier의 catch(ParseException·BadJOSEException·JOSEException)에 안 잡혀 500으로 나갔다. 외부 API의 일시 장애를 "서버 버그(500)"가 아니라 "재시도 가능한 의존성 장애 (503)"로 드러내기 위해, 통신 실패·비정상 응답을 SOCIAL_AUTH_UNAVAILABLE (503) 도메인 예외로 번역한다.
kite707
approved these changes
Jul 19, 2026
kite707
left a comment
Collaborator
There was a problem hiding this comment.
SOCIAL_AUTH_UNAVAILABLE(503)로 새 에러코드를 만드시면서 200이 아닌 경우(구글이 4xx/5xx 주는 상황)까지 같이 커버해주신 부분이 좋았습니다.
테스트코드까지 꼼꼼히 잘 챙겨주셨네요!
확인한 내용대로 잘 반영되어서 승인합니다. 고생하셨습니다!
theminjunchoi
added a commit
that referenced
this pull request
Jul 19, 2026
Firebase 전환(#10)으로 앱이 더는 이 값을 읽지 않는다. CD의 .env 생성부와 docker-compose(dev·prod)에서 주입하던 죽은 설정을 정리한다. 앱이 실제로 읽는 env(DB_PASSWORD·JWT_SECRET 등)만 남긴다.
theminjunchoi
added a commit
that referenced
this pull request
Jul 21, 2026
* chore: 배포용 Dockerfile 추가
멀티스테이지 빌드로 실행 가능한 이미지를 만든다.
- build 스테이지: Gradle로 boot jar 빌드(테스트 제외)
- runtime 스테이지: JRE 21 경량 이미지, 비루트 유저 실행
- plain jar 비활성화로 boot jar만 패키징
- .dockerignore로 빌드 컨텍스트 최소화
* chore: 이미지 빌드·push 워크플로 추가
dev·prod 브랜치 push 시 앱 이미지를 빌드해 GitHub Container Registry에 push한다.
- ghcr.io/nexters/gamss-server 에 브랜치명·커밋 SHA 태그로 push
- GITHUB_TOKEN으로 인증(별도 시크릿 불필요)
- buildx gha 캐시로 빌드 가속
* chore: 배포 서버 세팅 스크립트 추가
가비아 VM 생성 후 서버를 준비하는 초기 세팅 스크립트를 추가한다.
- deploy/setup-server.sh: Docker 설치·방화벽·배포 유저·디렉토리 (멱등)
* chore: dev/prod 환경 설정 및 docker-compose 추가
환경별 프로필과 서버용 docker-compose를 추가한다. 각 서버에서 앱과
MySQL을 함께 띄우며, 2GB 서버 기준으로 튜닝했다.
- application-dev.yml, application-prod.yml: datasource를 env로 주입
- deploy/{dev,prod}/docker-compose.yml: app + MySQL(외부 포트 비공개)
- 앱 힙 -Xmx768m, MySQL buffer pool 384M (사양 올리면 이 값만 상향)
* chore: actuator 헬스체크 엔드포인트 추가
배포 검증·모니터링용 /actuator/health를 추가한다.
- spring-boot-starter-actuator 의존성
- health 엔드포인트만 노출, show-details=never
- /actuator/health는 인증 없이 접근 허용(배포 헬스체크용)
* chore: CD 배포 워크플로 및 배포 스크립트 추가
이미지 빌드 후 dev/prod 서버에 SCP·SSH로 배포하는 job을 추가한다.
브랜치명으로 GitHub Environment를 골라 환경별 시크릿을 주입한다.
배포는 단순 재시작 방식이다(무중단 아님). 헬스체크에 실패하면
이전 이미지 태그로 되돌린다. 롤백 기준이 되는 IMAGE_TAG는
서버 .env에 기록해 두고 재배포 시 보존한다.
* chore: 서버 사양에 맞춰 컨테이너 메모리 재배분
2GB 전제로 잡혀 있던 값을 실제 서버 사양(2vCore/8GB)에 맞춘다.
app 힙 768m→3g, MySQL buffer pool 384M→2G.
LLM 호출은 응답까지 스레드를 붙잡고 있어 동시성을 올리려면
스레드를 늘려야 하므로, 스택용으로 약 1.2G를 남겨 둔다.
* feat: nginx 리버스 프록시 추가
앱이 루프백에만 바인딩돼 있어 외부에서 접근할 수 없던 것을 nginx로 연다.
80 포트를 받아 gamss 네트워크의 app:8080 으로 프록시한다.
앱 컨테이너는 배포마다 재생성되며 IP가 바뀌는데, proxy_pass 에 이름을
그대로 쓰면 nginx가 기동 시점 IP를 캐시해 재배포 후 502가 난다.
Docker 내장 DNS를 resolver로 두고 변수로 넘겨 매번 다시 해석하게 했다.
actuator는 외부에 노출하지 않는다. LLM 응답을 고려해 프록시 타임아웃은
120초로 잡았다. 도메인이 정해지면 server_name 과 443 블록을 추가한다.
* fix: 배포 대상이 아닌 브랜치에서 이미지 빌드가 도는 것 방지
이미지 태그에 브랜치명을 쓰는데 Docker 태그는 슬래시를 허용하지 않는다.
deploy job에만 가드가 있어서, workflow_dispatch로 feat/x 같은 브랜치에서
실행하면 빌드를 전부 끝낸 뒤 push 단계에서 invalid reference format 으로
죽는다. build-and-push 에도 같은 가드를 걸어 아예 시작하지 않게 한다.
* fix: 테스트 통과 후에만 배포되도록 CI를 CD에 연결
ci.yml 과 image.yml 이 각각 push 에 반응해 병렬로 돌아서, 테스트가
깨져도 배포가 그대로 나가고 있었다.
ci.yml 을 workflow_call 로 열고 Build & Deploy 가 호출하게 한다.
push 트리거는 제거한다 — 남겨두면 테스트가 두 번 돌 뿐 아니라
배포를 막지 못하는 문제가 그대로다.
concurrency 그룹은 배포·롤백이 한 환경에서 겹치지 않도록 deploy-{환경}
으로 통일하고, cancel-in-progress 를 끈다. SSH로 원격 상태를 바꾸는
중에 취소당하면 .env 만 갱신되고 컨테이너는 그대로인 상태가 남는다.
* feat: 수동 롤백 워크플로 추가
배포는 성공했으나 버그가 발견된 경우 이전 이미지 태그로 되돌린다.
헬스체크 실패로 인한 롤백은 배포 중 deploy-app.sh 가 이미 처리하므로,
이 워크플로는 그 이후에 사람이 판단해 되돌리는 경로를 담당한다.
서버에 있는 deploy-app.sh 를 재사용해 헬스체크·자동복구 동작을 배포와
동일하게 가져간다. 태그가 대상 환경 접두사와 다르면 실행 전에 막아
prod 에 dev 이미지를 올리는 사고를 방지한다.
* chore: OAuth client-ids 누락 시 부팅 실패 대신 경고로 완화
dev/prod 에서 client-ids 가 비면 부팅을 실패시키던 것을, 경고 로그만 남기고
부팅은 계속하도록 바꾼다. client-ids 발급 전에 배포 파이프라인을 검증할 수
있게 하기 위함이다.
client-ids 가 비어도 aud 검증은 여전히 fail-closed 라, 소셜 로그인은 안전하게
막힌 채 서버만 뜬다. 설정 누락은 기동 시점 경고로 드러난다(배포 전 필수 설정).
* chore: 쓰이지 않는 GOOGLE·APPLE_CLIENT_IDS 배포 설정 제거
Firebase 전환(#10)으로 앱이 더는 이 값을 읽지 않는다. CD의 .env 생성부와
docker-compose(dev·prod)에서 주입하던 죽은 설정을 정리한다. 앱이 실제로 읽는
env(DB_PASSWORD·JWT_SECRET 등)만 남긴다.
* fix: 배포 스크립트 롤백 누락·워크플로 권한·인젝션 보완
CodeRabbit 리뷰 반영.
- deploy-app.sh: set -e 하에서 `docker compose up -d`가 실패하면 스크립트가
즉시 종료돼 자동 롤백이 안 되던 것을, pull 과 동일하게 `if !`로 감싼다.
main은 롤백으로, rollback 내부는 경고 후 반환하도록 처리.
- image.yml·rollback.yml: 워크플로 기본 packages 권한을 write→read 로 낮추고,
GHCR push 가 필요한 build-and-push job 에만 write 를 부여(최소 권한).
- rollback.yml: 자유 형식 입력(image_tag 등)을 Summary 스크립트에 직접 보간하던
것을 env 로 받아 셸 변수로 참조(스크립트 인젝션 방지).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔗 연관 이슈
📌 개요
앱이 Firebase Authentication으로 로그인하기로 해, 서버의 소셜 로그인 검증을 구글/애플 원본 토큰 검증(native OIDC) 에서 Firebase ID 토큰 검증으로 전환합니다. 우리 서비스 토큰(access·refresh) 발급·재발급·해싱 로직은 그대로 유지되고, "들어온 소셜 토큰을 검증하는" 레이어만 교체됩니다.
🔧 주요 변경사항
검증 레이어 교체
SocialTokenVerifier인터페이스 +FirebaseTokenVerifier구현https://securetoken.google.com/{projectId}, aud{projectId}, 서명·만료 검증firebase.sign_in_provider클레임에서 판별sub(Firebase UID)FirebaseJwkSource— 구글 securetoken x509 인증서 엔드포인트에서 공개키를 가져와 Cache-Control 만큼 캐싱 (표준 JWKS 형식이 아니라 직접 파싱)API 단순화
POST /api/auth/login/{provider}→POST /api/auth/login단일 엔드포인트설정
gamss-cbdcb)는 비밀이 아니라application.yml설정값으로 둠GOOGLE_CLIENT_IDS·APPLE_CLIENT_IDS시크릿과 기동 검증(OAuthClientIdsValidator)이 불필요해짐네이밍 정리 (Firebase 전환에 맞춤)
auth.oauth→auth.socialOAuthProvider→SocialProviderErrorCode.UNSUPPORTED_OAUTH_PROVIDER→UNSUPPORTED_SOCIAL_PROVIDERSwagger 문서 보강 (앱 연동용)
ApiResponse·ErrorResponse에@Schema설명 추가 (공통 응답 포맷·error.code분기)error.code목록 명시제거 (native OIDC 레이어)
OidcTokenVerifier,GoogleOAuthClient/AppleOAuthClient,OAuthClientResolver,AllowedAudiences,OAuthProperties,OAuthClientIdsValidator,JwkSources,OAuthUserInfo유지
JwtIssuer,TokenHasher),AuthService/LoginService흐름,ConflictRetry(동시 로그인 재시도),SocialAccount(provider·providerId=UID)🌐 API · DB 영향
POST /api/auth/login/{provider}→POST /api/auth/login(body는{ idToken }동일, idToken이 이제 Firebase ID 토큰)social_accounts.provider_id에 Firebase UID를 저장 — 스키마 불변)💬 리뷰 포인트
1. x509 공개키 소스 — end-to-end 검증됨 — Firebase securetoken은 표준 JWKS가 아니라
{kid: PEM인증서}형식이라FirebaseJwkSource에서 직접 파싱·캐싱합니다. openssl로 만든 실제 키쌍으로 파싱 → 공개키 → 짝 개인키가 서명한 토큰 검증 성공까지 테스트했습니다(FirebaseJwkSourceTest). 검증 로직 자체는 로컬 키 주입FirebaseTokenVerifierTest가 커버합니다.2. 키 엔드포인트 장애 시 500 — Firebase 공개키 서버가 다운되면
FirebaseJwkSource의 IOException이 500으로 나갑니다(서버 측 의존성 장애). 기존 native 코드도 동일했고, 클라이언트 오류가 아니므로 그대로 뒀습니다.📋 앱 팀과 합의할 것
POST /api/auth/login에 Firebase ID 토큰(user.getIdToken())을 보낼 것테스트 101개 통과 (Firebase 토큰 검증 · x509 파싱·서명 검증 · 동시 로그인 경합 재현 포함).