Skip to content

feat: 구글 로그인 기능 구현 - #18

Merged
young0206 merged 14 commits into
developfrom
feature/14-google-login
Jul 19, 2026
Merged

feat: 구글 로그인 기능 구현#18
young0206 merged 14 commits into
developfrom
feature/14-google-login

Conversation

@sangwon02

@sangwon02 sangwon02 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

🔗 관련 이슈 (Related Issue)

Closes #14

📝 작업 내용

구글 OAuth 로그인 기능 구현

  • 구글 로그인 진입 및 콜백 처리 (계정 없으면 자동 회원가입)
  • 리프레시 토큰은 쿠키로 관리, DB에 저장/무효화
  • 액세스 토큰 재발급 API
  • 로그아웃 API (토큰 무효화 + 쿠키 삭제)

✅ PR 체크리스트

  • PR 제목은 커밋 컨벤션을 따랐습니다.
  • 관련 이슈를 연결했습니다.
  • 변경 사항에 대한 테스트를 진행했습니다.

Summary by CodeRabbit

  • 새 기능

    • Google 계정으로 로그인하고 기존 계정과 소셜 계정을 연결할 수 있습니다.
    • 로그인 성공 시 액세스 토큰과 리프레시 토큰을 발급합니다.
    • 만료된 액세스 토큰을 리프레시 토큰으로 재발급할 수 있습니다.
    • 로그아웃 시 리프레시 토큰을 안전하게 폐기합니다.
    • OAuth 인증 상태와 토큰을 보안 쿠키로 관리합니다.
  • 보안

    • 인증이 필요한 API 요청에 JWT 기반 인증을 적용했습니다.
    • 인증되지 않은 요청에 일관된 오류 응답을 제공합니다.
    • Swagger 문서에서 Bearer 토큰 인증을 사용할 수 있습니다.

sangwon02 added 14 commits July 18, 2026 16:58
- Spring Security + jjwt 의존성 추가, JWT 필터/엔트리포인트 구성
- RefreshToken 엔티티와 리포지토리 추가 (만료 시각 함께 저장)
- Users에 onboardingCompleted 추가 및 소셜 가입 정적 팩토리 추가
- 구글 OAuth/JWT/쿠키 설정을 환경변수 기반으로 분리
GET /api/v1/auth/login/google

- CSRF 방어용 state를 생성해 HttpOnly 쿠키로 저장
- redirectTo는 화이트리스트에 있는 경로만 허용해 오픈 리다이렉트 방지
- 구글 인증 페이지로 302 리다이렉트
GET /api/v1/auth/callback/google

- state 쿠키와 대조해 CSRF 검증, 불일치 시 INVALID_STATE로 에러 페이지 이동
- 인가 코드를 토큰으로 교환하고 구글 프로필 조회
- 미가입 시 유저 생성, 같은 이메일의 기존 계정이 있으면 구글 계정 연동
- 리프레시 토큰을 DB에 저장하고 HttpOnly 쿠키로 발급 후 프론트 콜백 페이지로 리다이렉트
POST /api/v1/auth/refresh

- 쿠키의 리프레시 토큰을 DB 저장분과 대조하고 서명/만료 검증
- 무효하거나 만료된 경우 도메인 코드 AUTH401 응답
- 회전(rotation) 도입 지점에 TODO 표기
POST /api/v1/auth/logout

- DB에 저장된 리프레시 토큰 삭제
- Max-Age=0 으로 리프레시 토큰 쿠키 만료 처리
- 액세스 토큰 인증이 필요한 엔드포인트로 구성
- 새로 추가된 리포지토리를 MockitoBean으로 대체
- app.* 설정 바인딩을 위한 테스트 프로퍼티 추가
기존에는 DataSource/JPA 오토컨피그를 제외한 채 @EnableJpaAuditing이 동작해
'JPA metamodel must not be empty'로 contextLoads가 실패하고 있었다.

- 테스트 런타임에 H2 추가, 제외 설정을 걷어내고 실제 JPA로 기동
- ddl-auto=create-drop으로 엔티티 매핑까지 스모크 테스트가 검증하도록 변경
- 리포지토리 MockitoBean 제거
로컬 http 환경에서 브라우저가 Secure 쿠키를 저장하지 않아 콜백 이후 흐름을
확인할 수 없어, COOKIE_SECURE로 낮출 수 있게 한다. 기본값은 true.
구글에서 콜백으로 돌아오는 요청은 크로스 사이트 이동이라
SameSite=Strict인 state 쿠키가 브라우저에서 전송되지 않아
정상 로그인도 INVALID_STATE로 실패하고 있었다.

- state 쿠키만 SameSite=Lax로 분리 (최상위 GET 이동에는 전송되어 CSRF 방어는 유지)
- 리프레시 토큰 쿠키는 명세대로 Strict 유지
인증 스킴이 없어 Authorize 버튼이 노출되지 않아 토큰이 필요한 API를
Swagger UI에서 테스트할 수 없었다.
- callback/google은 구글만 호출하므로 @hidden으로 문서에서 제외
- login/google은 브라우저 이동용임을 설명에 명시
- 인증이 필요 없는 login/google, refresh는 @SecurityRequirements로 표시
- UserRepository: develop의 조회 메서드와 소셜 로그인용 메서드를 통합
- application.yml: youtube 설정과 app(인증) 설정을 함께 유지
- 테스트 설정에 youtube 프로퍼티 추가, .env.example에 YOUTUBE_API_KEY 명시
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Google OAuth 로그인과 콜백 처리를 추가하고, JWT 기반 요청 인증 및 리프레시 토큰 재발급·로그아웃 API를 구현했습니다. 관련 설정 프로퍼티, 쿠키 처리, 사용자 소셜 계정 연동, 토큰 저장용 데이터베이스 구조와 테스트 환경도 추가되었습니다.

Changes

Google 인증 기능

Layer / File(s) Summary
인증 설정 및 애플리케이션 구성
.env.example, build.gradle, src/main/java/com/slatto/..., src/main/resources/application.yml, src/test/...
JWT, Google OAuth, 프론트엔드 리다이렉트, 쿠키 설정과 테스트용 H2 구성이 추가되었습니다.
JWT 보안 필터 구성
src/main/java/com/slatto/global/security/*, src/main/java/com/slatto/global/config/SecurityConfig.java, src/main/java/com/slatto/global/config/SwaggerConfig.java
JWT 생성·검증, Bearer 토큰 인증 주입, 인증 실패 응답, 무상태 보안 필터 체인과 Swagger Bearer 인증이 추가되었습니다.
Google OAuth 및 쿠키 계약
src/main/java/com/slatto/domain/auth/client/*, src/main/java/com/slatto/domain/auth/support/*
Google 인가 URL·토큰 교환·사용자 정보 조회, OAuth state 직렬화, OAuth 실패 사유와 인증 쿠키 생성 로직이 추가되었습니다.
사용자 및 리프레시 토큰 저장
src/main/java/com/slatto/domain/auth/entity/*, src/main/java/com/slatto/domain/auth/repository/*, src/main/java/com/slatto/domain/user/*, src/main/resources/db/migration/*
소셜 사용자 생성·연결과 리프레시 토큰 엔티티·저장소가 추가되고 데이터베이스 테이블 및 사용자 컬럼 마이그레이션이 정의되었습니다.
인증 서비스 및 HTTP 엔드포인트
src/main/java/com/slatto/domain/auth/service/AuthService.java, src/main/java/com/slatto/domain/auth/controller/AuthController.java, src/main/java/com/slatto/domain/auth/dto/AccessTokenResponse.java, src/main/java/com/slatto/domain/auth/exception/AuthErrorCode.java
Google 로그인 콜백, 사용자 조회·생성, 리프레시 토큰 발급·재발급·로그아웃 API가 구현되었습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthController
  participant AuthService
  participant GoogleOAuthClient
  participant RefreshTokenRepository

  Client->>AuthController: Google 로그인 요청
  AuthController->>AuthService: 로그인 진입 정보 생성
  AuthService->>GoogleOAuthClient: Google 인가 URI 생성
  AuthController-->>Client: state 쿠키 설정 및 Google 리다이렉트
  Client->>AuthController: OAuth callback 전달
  AuthController->>AuthService: callback 처리
  AuthService->>GoogleOAuthClient: 인증 코드 교환 및 사용자 조회
  AuthService->>RefreshTokenRepository: 리프레시 토큰 저장
  AuthController-->>Client: 프론트엔드 리다이렉트 및 토큰 쿠키 설정
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 구글 로그인 기능 구현이라는 핵심 변경을 간결하게 잘 요약합니다.
Description check ✅ Passed 관련 이슈, 작업 내용, 테스트 체크리스트가 템플릿 요구사항을 모두 충족합니다.
Linked Issues check ✅ Passed #14의 구글 로그인, 콜백, 토큰 재발급, 로그아웃 요구사항이 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 변경은 인증 기능과 그에 필요한 설정·테스트·문서 보강에 집중되어 범위 이탈이 보이지 않습니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
.env.example (1)

20-21: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

로컬 HTTP 예시와 COOKIE_SECURE 값이 충돌합니다.

기본 redirect/backend/frontend 주소가 모두 http://localhost인데 COOKIE_SECURE=true입니다. 예시를 그대로 사용하면 브라우저가 OAuth state 및 refresh 쿠키를 콜백·refresh 요청에 포함하지 않을 수 있습니다. 로컬 예시는 false로 두고 운영 환경에서만 true로 덮어쓰는 구성이 안전합니다.

제안
-COOKIE_SECURE=true
+# 로컬 HTTP 테스트용. 운영에서는 HTTPS와 true를 사용
+COOKIE_SECURE=false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.example around lines 20 - 21, .env.example의 로컬 HTTP 설정과 COOKIE_SECURE
기본값이 일치하도록 COOKIE_SECURE를 false로 변경하고, 운영 환경에서는 별도 환경 설정으로 true를 지정하도록 유지하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/slatto/domain/auth/client/GoogleOAuthClient.java`:
- Around line 19-21: Update the GoogleOAuthClient constructor to configure
connection and response/read timeouts on the RestClient.Builder before build(),
matching the timeout behavior used by YoutubeApiClient or the project’s shared
builder defaults. Apply these settings to the restClient used for Google token
and user-information requests.

In `@src/main/java/com/slatto/domain/auth/entity/RefreshToken.java`:
- Around line 30-31: Update the RefreshToken token persistence flow around the
token field to store only an HMAC digest derived with a server-side secret,
never the raw bearer token. Apply the same deterministic digesting to incoming
cookie tokens before refresh-token lookup and deletion, while keeping token
issuance and client-facing cookie values unchanged.

In `@src/main/java/com/slatto/domain/auth/service/AuthService.java`:
- Around line 127-131: Update issueRefreshToken to atomically replace the user’s
refresh token: enforce a unique user_id constraint for refreshTokenRepository’s
entity, and use a transaction-safe lock or atomic upsert covering deletion and
saving. Ensure concurrent login requests cannot leave multiple tokens valid
through findByToken, preserving only the latest token.
- Around line 70-75: Update the Google authentication validation around userInfo
and findOrCreateUser to handle a missing userInfo.name() before persistence;
either generate a valid default nickname or return AUTH_FAILED explicitly,
ensuring findOrCreateUser never receives a null name for the non-null nickname
field.
- Around line 77-79: Update handleGoogleCallback() to validate the
cookie-derived storedState.redirectPath() through the existing
resolveRedirectPath() whitelist logic before passing it to
frontendProperties.toAbsoluteUrl(). Preserve the validated redirect path in the
GoogleCallbackResult.

In `@src/main/java/com/slatto/domain/user/repository/UserRepository.java`:
- Around line 15-17: Update UserRepository methods findBySocialTypeAndSocialId
and findByEmail to include a DeletedAtIsNull condition, then update the
corresponding AuthService calls, including findOrCreateUser, to use the revised
repository method signatures so soft-deleted users are excluded from
authentication lookups.

In `@src/main/java/com/slatto/global/config/SecurityConfig.java`:
- Around line 30-37: SecurityConfig의 SecurityFilterChain에 CORS 설정을 추가하고
CorsConfigurationSource를 연결하세요. FRONTEND_BASE_URL을 허용 origin으로 사용하고 필요한 HTTP
메서드와 요청 헤더를 명시하며 credentials 허용을 활성화해 preflight 및 인증 포함 refresh 요청이 통과하도록 구성하세요.

In `@src/main/resources/db/migration/001-auth-google-login.sql`:
- Around line 1-19: Connect the migration containing onboarding_completed and
refresh_token to the deployment path so it is applied automatically before the
application starts. Configure and enable the project’s migration mechanism, or
add an atomic deployment step that executes this SQL for new and existing
environments; do not leave it as an unreferenced resource while ddl-auto remains
validate.

---

Nitpick comments:
In @.env.example:
- Around line 20-21: .env.example의 로컬 HTTP 설정과 COOKIE_SECURE 기본값이 일치하도록
COOKIE_SECURE를 false로 변경하고, 운영 환경에서는 별도 환경 설정으로 true를 지정하도록 유지하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8b7318f-bf1d-4fca-a900-95f9a8e86ce4

📥 Commits

Reviewing files that changed from the base of the PR and between 9726e69 and 00517f1.

📒 Files selected for processing (31)
  • .env.example
  • .gitignore
  • build.gradle
  • src/main/java/com/slatto/SlattoApplication.java
  • src/main/java/com/slatto/domain/auth/client/GoogleOAuthClient.java
  • src/main/java/com/slatto/domain/auth/client/dto/GoogleTokenResponse.java
  • src/main/java/com/slatto/domain/auth/client/dto/GoogleUserInfo.java
  • src/main/java/com/slatto/domain/auth/controller/AuthController.java
  • src/main/java/com/slatto/domain/auth/dto/AccessTokenResponse.java
  • src/main/java/com/slatto/domain/auth/entity/RefreshToken.java
  • src/main/java/com/slatto/domain/auth/exception/AuthErrorCode.java
  • src/main/java/com/slatto/domain/auth/repository/RefreshTokenRepository.java
  • src/main/java/com/slatto/domain/auth/service/AuthService.java
  • src/main/java/com/slatto/domain/auth/support/AuthCookieFactory.java
  • src/main/java/com/slatto/domain/auth/support/GoogleAuthFailureReason.java
  • src/main/java/com/slatto/domain/auth/support/OAuthState.java
  • src/main/java/com/slatto/domain/user/entity/Users.java
  • src/main/java/com/slatto/domain/user/repository/UserRepository.java
  • src/main/java/com/slatto/global/config/SecurityConfig.java
  • src/main/java/com/slatto/global/config/SwaggerConfig.java
  • src/main/java/com/slatto/global/config/properties/CookieProperties.java
  • src/main/java/com/slatto/global/config/properties/FrontendProperties.java
  • src/main/java/com/slatto/global/config/properties/GoogleOAuthProperties.java
  • src/main/java/com/slatto/global/config/properties/JwtProperties.java
  • src/main/java/com/slatto/global/security/JwtAuthenticationEntryPoint.java
  • src/main/java/com/slatto/global/security/JwtAuthenticationFilter.java
  • src/main/java/com/slatto/global/security/JwtTokenProvider.java
  • src/main/resources/application.yml
  • src/main/resources/db/migration/001-auth-google-login.sql
  • src/test/java/com/slatto/SlattoApplicationTests.java
  • src/test/resources/application.yml

Comment on lines +19 to +21
public GoogleOAuthClient(GoogleOAuthProperties googleOAuthProperties, RestClient.Builder restClientBuilder) {
this.googleOAuthProperties = googleOAuthProperties;
this.restClient = restClientBuilder.build();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C3 -g '*.java' -g '*.kt' \
  'RestClient\.builder|RestClient\.Builder|requestFactory|connectTimeout|readTimeout|setConnectTimeout|setReadTimeout' \
  src/main/java

rg -n -C3 -g '*.yml' -g '*.yaml' -g '*.properties' \
  'timeout|restclient|http' \
  src/main/resources

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 4652


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== RestClient.Builder / RestClient customization =="
rg -n -C3 -g '*.java' -g '*.kt' \
  'RestClient\.Builder|RestClientCustomizer|RestClient.Builder|requestFactory|setConnectTimeout|setReadTimeout|connect-timeout|read-timeout|timeout' \
  src/main/java src/test/java

echo
echo "== Bean definitions likely affecting HTTP clients =="
rg -n -C3 -g '*.java' -g '*.kt' \
  '`@Bean`|`@Configuration`|ClientHttpRequestFactory|SimpleClientHttpRequestFactory|HttpComponentsClientHttpRequestFactory|OkHttp3ClientHttpRequestFactory' \
  src/main/java

echo
echo "== All auth-related config =="
sed -n '1,120p' src/main/resources/application.yml

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 12208


Google OAuth RestClient에 타임아웃을 추가하세요.

GoogleOAuthClientRestClient.Builder를 바로 build()만 하고 있어, Google 토큰/사용자정보 호출에 연결·응답 제한 시간이 없습니다. YoutubeApiClient처럼 이 클라이언트에도 connect/read timeout을 넣거나 공통 builder에 기본 타임아웃을 설정하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/domain/auth/client/GoogleOAuthClient.java` around
lines 19 - 21, Update the GoogleOAuthClient constructor to configure connection
and response/read timeouts on the RestClient.Builder before build(), matching
the timeout behavior used by YoutubeApiClient or the project’s shared builder
defaults. Apply these settings to the restClient used for Google token and
user-information requests.

Comment on lines +30 to +31
@Column(name = "token", nullable = false, length = 512, unique = true)
private String token;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

리프레시 토큰을 평문으로 저장하지 마세요.

Line 30은 재사용 가능한 bearer 토큰을 그대로 저장합니다. DB 읽기 권한 유출만으로 세션 탈취가 가능하므로, 서버 비밀키 기반 HMAC 다이제스트를 저장하고 조회·삭제 시에도 쿠키 토큰을 동일하게 다이제스트하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/domain/auth/entity/RefreshToken.java` around lines
30 - 31, Update the RefreshToken token persistence flow around the token field
to store only an HMAC digest derived with a server-side secret, never the raw
bearer token. Apply the same deterministic digesting to incoming cookie tokens
before refresh-token lookup and deletion, while keeping token issuance and
client-facing cookie values unchanged.

Comment on lines +70 to +75
if (userInfo == null || userInfo.email() == null || !userInfo.isEmailVerified()) {
return failure(GoogleAuthFailureReason.AUTH_FAILED);
}

Users user = findOrCreateUser(userInfo);
String refreshToken = issueRefreshToken(user);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

ast-grep outline src/main/java/com/slatto/domain/auth/client/GoogleOAuthClient.java --items all
rg -n -C4 '(scope|profile|name)' \
  src/main/java/com/slatto/domain/auth/client/GoogleOAuthClient.java \
  src/main/java/com/slatto/global/config/properties/GoogleOAuthProperties.java

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 2567


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

ast-grep outline src/main/java/com/slatto/domain/auth/service/AuthService.java --items all
ast-grep outline src/main/java/com/slatto/domain/auth/client/dto/GoogleUserInfo.java --items all
ast-grep outline src/main/java/com/slatto/domain/user/domain/Users.java --items all

sed -n '1,220p' src/main/java/com/slatto/domain/auth/service/AuthService.java
printf '\n--- GOOGLE USER INFO ---\n'
sed -n '1,220p' src/main/java/com/slatto/domain/auth/client/dto/GoogleUserInfo.java
printf '\n--- USERS ---\n'
sed -n '1,220p' src/main/java/com/slatto/domain/user/domain/Users.java

printf '\n--- SCOPE REFERENCES ---\n'
rg -n -C3 'googleOAuthProperties\.scope\(|scope:' src/main/resources src/main/java

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 7462


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

fd -a 'Users.java' src/main/java
fd -a 'application*.y*ml' src/main/resources

printf '\n--- USERS OUTLINE ---\n'
ast-grep outline src/main/java/com/slatto/domain/user/entity/Users.java --items all

printf '\n--- USERS FILE ---\n'
sed -n '1,260p' src/main/java/com/slatto/domain/user/entity/Users.java

printf '\n--- GOOGLE OAUTH PROPERTIES / SCOPE ---\n'
rg -n -C4 'scope|google' src/main/resources src/main/java/com/slatto/global/config/properties

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 5735


🌐 Web query:

Google OAuth userinfo endpoint name claim profile scope optional returned when name is missing documentation

💡 Result:

When using the Google OAuth 2.0 / OpenID Connect userinfo endpoint, the name, given_name, and family_name claims are not guaranteed to be present in the response [1][2][3]. Even when the profile scope is requested, these fields may be omitted for several reasons [1][4][5]: 1. User/Organization Settings: Users or their organizations may choose to withhold specific profile fields [1][4]. 2. Data Availability: The information simply may not be set or available in the user's Google account profile [4][5]. 3. Scope Limitations: While the profile scope is required to signal a request for this information, it does not act as a guarantee that the data will be provided [1][2]. Official Google documentation explicitly notes that the name claim "is never guaranteed to be present" [1]. Because these fields are optional, applications should implement defensive programming when handling the userinfo response [6][7]. If your application strictly requires user profile information that is not available via these claims, Google recommends using the Google People API (specifically the people.get method) as a more reliable alternative for retrieving comprehensive profile data [4][5]. Top results: [1][4][6][3]

Citations:


Google name 누락을 처리하세요. 이미 profile scope를 요청해도 name은 보장되지 않습니다. 현재 findOrCreateUser()userInfo.name()nickname(nullable=false)에 그대로 넣어, 일부 계정에서는 콜백이 DB 예외로 실패합니다. 기본 닉네임을 생성하거나, name이 없을 때 명시적으로 인증 실패로 처리하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/domain/auth/service/AuthService.java` around lines
70 - 75, Update the Google authentication validation around userInfo and
findOrCreateUser to handle a missing userInfo.name() before persistence; either
generate a valid default nickname or return AUTH_FAILED explicitly, ensuring
findOrCreateUser never receives a null name for the non-null nickname field.

Comment on lines +77 to +79
return new GoogleCallbackResult(
frontendProperties.toAbsoluteUrl(storedState.redirectPath()),
refreshToken,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

ast-grep outline src/main/java/com/slatto/global/config/properties/FrontendProperties.java --items all
rg -n -A20 -B5 'resolveRedirectPath|toAbsoluteUrl' \
  src/main/java/com/slatto/global/config/properties/FrontendProperties.java

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 727


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

ast-grep outline src/main/java/com/slatto/domain/auth/service/AuthService.java --items all
ast-grep outline src/main/java/com/slatto/domain/auth/model --items all
rg -n -A30 -B10 'stateCookieValue|redirectPath|resolveRedirectPath|toAbsoluteUrl|GoogleCallbackResult|StoredState|State' \
  src/main/java/com/slatto/domain/auth

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 29043


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

wc -l src/main/java/com/slatto/domain/auth/service/AuthService.java
cat -n src/main/java/com/slatto/domain/auth/service/AuthService.java | sed -n '1,180p'

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 6548


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -A20 -B20 'stateCookieValue|setState|createState|parseState|State' src/main/java/com/slatto/domain/auth/service/AuthService.java

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 5087


콜백에서도 redirectPath를 다시 화이트리스트로 거르세요.
handleGoogleCallback()는 쿠키에서 꺼낸 storedState.redirectPath()를 그대로 toAbsoluteUrl()에 넣습니다. 초기 로그인 때만 resolveRedirectPath()를 적용하면, 쿠키를 바꾼 경우 허용되지 않은 프론트엔드 경로로 우회할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/domain/auth/service/AuthService.java` around lines
77 - 79, Update handleGoogleCallback() to validate the cookie-derived
storedState.redirectPath() through the existing resolveRedirectPath() whitelist
logic before passing it to frontendProperties.toAbsoluteUrl(). Preserve the
validated redirect path in the GoogleCallbackResult.

Comment on lines +127 to +131
private String issueRefreshToken(Users user) {
refreshTokenRepository.deleteByUser(user);

String token = jwtTokenProvider.createRefreshToken(user.getId());
refreshTokenRepository.save(RefreshToken.issue(user, token, jwtTokenProvider.refreshTokenExpiresAt()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

리프레시 토큰 교체를 원자화하세요.

Line 128의 삭제와 Line 131의 저장 사이에 동시 로그인 요청이 끼면 둘 이상의 토큰이 남을 수 있고, 이후 모두 findByToken으로 유효하게 처리됩니다. user_id 유니크 제약과 잠금/원자적 upsert를 도입해 “최신 토큰만 유효” 계약을 보장하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/domain/auth/service/AuthService.java` around lines
127 - 131, Update issueRefreshToken to atomically replace the user’s refresh
token: enforce a unique user_id constraint for refreshTokenRepository’s entity,
and use a transaction-safe lock or atomic upsert covering deletion and saving.
Ensure concurrent login requests cannot leave multiple tokens valid through
findByToken, preserving only the latest token.

Comment on lines +15 to +17
Optional<Users> findBySocialTypeAndSocialId(SocialType socialType, String socialId);

Optional<Users> findByEmail(String email);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

소프트 삭제된 사용자를 인증 조회에서 제외하세요.

findBySocialTypeAndSocialIdfindByEmail에는 DeletedAtIsNull 조건이 없습니다. 콜백의 findOrCreateUser가 이 결과를 우선 사용하므로, 삭제된 계정에도 소셜 계정이 다시 연결되고 리프레시 토큰이 발급될 수 있습니다. 두 조회를 삭제 제외 조건으로 변경하고 AuthService 호출도 함께 갱신하세요.

수정 예시
- Optional<Users> findBySocialTypeAndSocialId(SocialType socialType, String socialId);
- Optional<Users> findByEmail(String email);
+ Optional<Users> findBySocialTypeAndSocialIdAndDeletedAtIsNull(SocialType socialType, String socialId);
+ Optional<Users> findByEmailAndDeletedAtIsNull(String email);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/domain/user/repository/UserRepository.java` around
lines 15 - 17, Update UserRepository methods findBySocialTypeAndSocialId and
findByEmail to include a DeletedAtIsNull condition, then update the
corresponding AuthService calls, including findOrCreateUser, to use the revised
repository method signatures so soft-deleted users are excluded from
authentication lookups.

Comment on lines +30 to +37
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/api/v1/auth/login/**",
"/api/v1/auth/callback/**",
"/api/v1/auth/refresh"
).permitAll()
.requestMatchers(HttpMethod.GET, "/api/v1/health", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

# 전역 또는 MVC CORS 설정과 Spring Security CORS 활성화 여부를 확인합니다.
rg -nP -C 3 '(\.cors\s*\(|CorsConfigurationSource|CorsFilter|`@CrossOrigin`)' \
  src/main/java

# 프런트엔드 origin 설정과 프록시 관련 구성을 확인합니다.
rg -n -C 3 'FRONTEND_BASE_URL|base-url|proxy' \
  src/main/resources src/test/resources

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -eu

printf '\n== SecurityConfig ==\n'
sed -n '1,220p' src/main/java/com/slatto/global/config/SecurityConfig.java

printf '\n== CORS-related symbols ==\n'
rg -n -C 2 'CorsConfigurationSource|CorsFilter|`@CrossOrigin`|\.cors\s*\(' src/main/java || true

printf '\n== Frontend/proxy config symbols ==\n'
rg -n -C 2 'FRONTEND_BASE_URL|base-url|proxy|cors|allowed-origins|allowedOriginPatterns' src/main/resources src/test/resources src/main/java || true

printf '\n== File list around config ==\n'
git ls-files 'src/main/java/com/slatto/global/config/*'

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 3118


🏁 Script executed:

#!/bin/bash
set -eu

printf '\n== Repository tree (top levels) ==\n'
find . -maxdepth 2 -type f | sed 's#^\./##' | sort | head -n 200

printf '\n== SecurityConfig around the relevant section ==\n'
nl -ba src/main/java/com/slatto/global/config/SecurityConfig.java | sed -n '1,220p'

printf '\n== Any CORS configuration anywhere in repo ==\n'
rg -n -C 2 'CorsConfigurationSource|CorsFilter|`@CrossOrigin`|\.cors\s*\(' . || true

printf '\n== Any frontend origin / proxy config anywhere in repo ==\n'
rg -n -C 2 'FRONTEND_BASE_URL|base-url|proxy|allowed-origins|allowedOriginPatterns|credentials' . || true

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 1452


🏁 Script executed:

#!/bin/bash
set -eu

printf '\n== Frontend-related files ==\n'
git ls-files | rg '(^|/)(package.json|vite\.config|next\.config|nuxt\.config|webpack\.config|angular\.json|nginx|docker-compose|compose|Dockerfile|\.env|application.*\.ya?ml|application.*\.properties|README|docs)' || true

printf '\n== Files mentioning frontend or API base URLs ==\n'
rg -n -C 2 '(frontend|front-end|base url|base-url|api url|api-url|origin|proxy)' . || true

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 4261


SecurityConfig에 CORS 설정을 추가하세요.
FRONTEND_BASE_URL가 분리된 구성인데 이 SecurityFilterChain에는 http.cors()/CorsConfigurationSource가 없습니다. 브라우저에서 preflight나 credentials 포함 /api/v1/auth/refresh 요청이 막힐 수 있으니 허용 origin, 메서드, 헤더, credentials를 명시하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/global/config/SecurityConfig.java` around lines 30 -
37, SecurityConfig의 SecurityFilterChain에 CORS 설정을 추가하고 CorsConfigurationSource를
연결하세요. FRONTEND_BASE_URL을 허용 origin으로 사용하고 필요한 HTTP 메서드와 요청 헤더를 명시하며 credentials
허용을 활성화해 preflight 및 인증 포함 refresh 요청이 통과하도록 구성하세요.

Comment on lines +1 to +19
-- ddl-auto=validate 이므로 애플리케이션 기동 전에 직접 적용해야 한다.

ALTER TABLE users
ADD COLUMN onboarding_completed BIT(1) NOT NULL DEFAULT b'0';

CREATE TABLE refresh_token
(
id BIGINT NOT NULL AUTO_INCREMENT,
user_id BIGINT NOT NULL,
token VARCHAR(512) NOT NULL,
expires_at DATETIME(6) NOT NULL,
created_at DATETIME(6) NOT NULL,
updated_at DATETIME(6) NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_refresh_token_token (token),
KEY idx_refresh_token_user_id (user_id),
CONSTRAINT fk_refresh_token_user FOREIGN KEY (user_id) REFERENCES users (id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

fd -HI -t f 'build\.gradle(\.kts)?|application\.(yml|yaml|properties)' . \
  -x rg -n -C3 'flyway|liquibase|ddl-auto|sql\.init' {}

fd -HI -t f . src/main/resources/db/migration

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 526


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '\n## migration files\n'
fd -HI -t f . src/main/resources/db

printf '\n## application configs with migration / ddl settings\n'
for f in $(git ls-files 'src/main/resources/*application*' 'src/main/resources/**/*.yml' 'src/main/resources/**/*.yaml' 'src/main/resources/**/*.properties' 2>/dev/null); do
  echo "### $f"
  rg -n -C2 'flyway|liquibase|ddl-auto|sql\.init|schema|data' "$f" || true
done

printf '\n## build files with migration deps\n'
for f in $(git ls-files 'build.gradle' 'build.gradle.kts' 'settings.gradle' 'settings.gradle.kts' 2>/dev/null); do
  echo "### $f"
  rg -n -C2 'flyway|liquibase|hibernate|jpa|mysql|h2' "$f" || true
done

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 1513


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '## files under src/main/resources/db'
git ls-files 'src/main/resources/db/**' | sort

echo
echo '## exact application files'
git ls-files 'src/main/resources/**application*' | sort

echo
echo '## relevant snippets from application.yml'
f='src/main/resources/application.yml'
if [ -f "$f" ]; then
  sed -n '1,120p' "$f"
fi

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 2020


배포 경로에 마이그레이션을 연결하세요.
ddl-auto=validate만 사용하고 Flyway/Liquibase 설정도 없어서, 이 src/main/resources/db/migration/001-auth-google-login.sql은 자동 실행되지 않습니다. 신규 환경에서는 users.onboarding_completed/refresh_token 누락으로 인증이 실패할 수 있으니, 마이그레이션 도구를 붙이거나 배포 단계에서 원자적으로 적용되게 해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/db/migration/001-auth-google-login.sql` around lines 1 -
19, Connect the migration containing onboarding_completed and refresh_token to
the deployment path so it is applied automatically before the application
starts. Configure and enable the project’s migration mechanism, or add an atomic
deployment step that executes this SQL for new and existing environments; do not
leave it as an unreferenced resource while ddl-auto remains validate.

@young0206
young0206 merged commit 7178060 into develop Jul 19, 2026
2 checks passed
@guingguing
guingguing deleted the feature/14-google-login branch August 11, 2026 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEAT: 구글 소셜 로그인 기능

2 participants