feat: 구글 가입 약관 동의 흐름을 signupToken 기반으로 분리#140
Conversation
- LoggingAspect: SENSITIVE_PARAM_NAMES에 signupToken 추가 - GoogleSignupTokenService.create: 이전 토큰 무효화 + 신규 토큰 저장을 Lua 스크립트로 원자화하여 동일 providerId 동시 발급 race 제거 - GoogleSignupTokenService.consume: payload deserialize 실패 시 warn 로그 추가하여 운영 진단성 확보 - GoogleSignupTokenServiceTest: 손상된 JSON payload에 대한 consume 거부 테스트 추가 - AuthController.googleLogin: ResponseEntity<?> → ResponseEntity<GoogleLoginResponse>로 타입화하여 @apiresponse schema와 일치
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughGoogle OAuth 신규 사용자 약관 미동의 시 Redis 기반 임시 signup token(10분)을 발급하고, 프런트가 약관 동의 후 해당 토큰으로 POST /auth/google/signup을 호출해 가입을 완료하도록 흐름을 추가했습니다. ChangesGoogle OAuth Signup Token 플로우
Sequence DiagramsequenceDiagram
participant Client
participant AuthController
participant AuthService
participant GoogleSignupTokenService
participant Redis
Client->>AuthController: POST /auth/google/login<br/>code, agreementsAccepted=false
AuthController->>AuthService: googleLogin(code, false)
AuthService->>AuthService: Google OAuth 코드로 계정 조회
AuthService->>AuthService: 신규 사용자 확인
AuthService->>GoogleSignupTokenService: create(GoogleSignupPayload)
GoogleSignupTokenService->>GoogleSignupTokenService: UUID 토큰 생성
GoogleSignupTokenService->>Redis: Lua: 기존 토큰 삭제 후<br/>token → payload 저장<br/>providerId → token 저장<br/>TTL 10분 설정
GoogleSignupTokenService-->>AuthService: signupToken 반환
AuthService-->>AuthController: GoogleLoginOutcome.signupRequired()
AuthController-->>Client: 200 OK<br/>{ status: SIGNUP_REQUIRED,<br/>signupToken,<br/>expiresInSeconds: 600 }<br/>쿠키 없음
Client->>AuthController: POST /auth/google/signup<br/>signupToken, agreementsAccepted=true
AuthController->>AuthService: googleSignup(signupToken, true)
AuthService->>GoogleSignupTokenService: consume(signupToken)
GoogleSignupTokenService->>Redis: GET & DELETE token 키
GoogleSignupTokenService->>GoogleSignupTokenService: Payload JSON 역직렬화
GoogleSignupTokenService->>Redis: 최신 토큰 확인<br/>providerId → token 조회
alt 최신 토큰 일치
GoogleSignupTokenService->>Redis: providerId 키 삭제
GoogleSignupTokenService-->>AuthService: GoogleSignupPayload 반환
else 토큰 불일치
GoogleSignupTokenService-->>AuthService: CustomException<br/>GOOGLE_SIGNUP_TOKEN_NOT_FOUND
end
AuthService->>AuthService: Payload로 사용자 생성<br/>약관 버전/동의 시간 저장
AuthService->>AuthService: issueTokens(user)
AuthService-->>AuthController: LoginResult
AuthController-->>Client: 200 OK<br/>Set-Cookie: access_token<br/>Set-Cookie: refresh_token
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsStopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/howaboutus/backend/common/config/SecurityConfig.java (1)
46-60: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win웹소켓 permitAll 규칙을 일반 공개 경로와 분리해 주세요.
현재
"/ws/**"가 다른 공개 API와 같은requestMatchers(...)블록에 묶여 있습니다. 웹소켓 경로는 별도 matcher 체인으로 분리해 정책 의도를 명확히 해 주세요.변경 예시
.authorizeHttpRequests(authorize -> authorize .requestMatchers( "/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/springwolf/**", "/actuator/health", "/actuator/prometheus", "/actuator/caches", "/api/agreements/current", "/auth/google/login", "/auth/google/signup", "/auth/refresh", - "/auth/logout", - "/ws/**") + "/auth/logout") .permitAll() + .requestMatchers("/ws/**").permitAll() .anyRequest().authenticated())As per coding guidelines:
src/main/java/com/howaboutus/backend/common/**/*Security*.java에서는 WebSocket 엔드포인트를 다른 endpoint permission과 분리해 명시적으로 허용해야 합니다.🤖 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/howaboutus/backend/common/config/SecurityConfig.java` around lines 46 - 60, The SecurityConfig currently bundles "/ws/**" into the general requestMatchers(...) permitAll list; separate the WebSocket rule into its own matcher so intent is explicit. Locate the requestMatchers(...) call in SecurityConfig (e.g., inside the securityFilterChain or configure(HttpSecurity) method), remove "/ws/**" from that array and add a separate matcher chain for WebSocket endpoints such as .requestMatchers("/ws/**").permitAll() (placed appropriately among other matcher rules). Ensure the rest of the existing permitAll() list remains unchanged and that no other authorization rules are inadvertently reordered.Source: Coding guidelines
🧹 Nitpick comments (2)
docs/ai/features.md (1)
55-55: ⚡ Quick win메인 기능 명세 파일에 구현 상세가 과도하게 들어가 있습니다.
이 줄은 흐름/조건/저장 동작까지 너무 상세해서, 메인 문서 요약 역할을 벗어납니다.
docs/ai/하위 상세 문서로 분리하고 여기에는 “언제 어떤 문서를 참조할지”만 남겨 주세요.권장 수정 예시
-| `[x]` | 가입 약관 동의 기록 | Google 인가 코드는 1회만 사용할 수 있으므로 ... 신규 사용자가 첫 로그인 요청부터 `agreementsAccepted=true`를 보내면 즉시 생성한다 | users, Redis | +| `[x]` | 가입 약관 동의 기록 | Google 신규 가입 약관 동의 흐름은 signupToken 기반으로 분리되며, 상세 시퀀스/에러/TTL은 `docs/ai/auth-google-signup-token.md`를 참고한다 | users, Redis |As per coding guidelines, "
**/*.md: Split detailed explanations into separate documents underdocs/ai/, keeping this main file brief with only 'when to read which document' references".🤖 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 `@docs/ai/features.md` at line 55, The row describing "`가입 약관 동의 기록`" contains implementation-level details (flows, cookie behavior, signupToken, endpoints `POST /auth/google/login` and `POST /auth/google/signup`, storage of `users` and Redis) that should be moved into a new detailed spec under docs/ai/, leaving this main feature line as a brief pointer; create a new document (e.g., "auth-google-signup.md") with the full flow and storage details, replace the long cell in the main features table with a short reference like "See auth-google-signup.md for flow and storage details", and ensure the new doc documents the `SIGNUP_REQUIRED` response, `signupToken` TTL, agreementsAccepted handling, and what gets stored in users/Redis.Source: Coding guidelines
src/test/java/com/howaboutus/backend/auth/AuthIntegrationTest.java (1)
68-68: 💤 Low value만료 시간 검증에 magic number 대신 상수를 사용하는 것을 고려하세요.
Line 68에서
expiresInSeconds를 600으로 hardcoding하고 있습니다.GoogleSignupTokenService.EXPIRES_IN_SECONDS상수를 직접 참조하면 유지보수성이 향상됩니다.♻️ 제안하는 개선
.andExpect(status().isOk()) .andExpect(jsonPath("$.status").value("SIGNUP_REQUIRED")) - .andExpect(jsonPath("$.expiresInSeconds").value(600)) + .andExpect(jsonPath("$.expiresInSeconds").value(GoogleSignupTokenService.EXPIRES_IN_SECONDS)) .andExpect(cookie().doesNotExist("access_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/test/java/com/howaboutus/backend/auth/AuthIntegrationTest.java` at line 68, Replace the hardcoded magic number in the test assertion with the constant from the token service: change the expiresInSeconds expectation in AuthIntegrationTest to use GoogleSignupTokenService.EXPIRES_IN_SECONDS (or a static import of it) instead of 600 so the test stays in sync with the implementation.
🤖 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.
Outside diff comments:
In `@src/main/java/com/howaboutus/backend/common/config/SecurityConfig.java`:
- Around line 46-60: The SecurityConfig currently bundles "/ws/**" into the
general requestMatchers(...) permitAll list; separate the WebSocket rule into
its own matcher so intent is explicit. Locate the requestMatchers(...) call in
SecurityConfig (e.g., inside the securityFilterChain or configure(HttpSecurity)
method), remove "/ws/**" from that array and add a separate matcher chain for
WebSocket endpoints such as .requestMatchers("/ws/**").permitAll() (placed
appropriately among other matcher rules). Ensure the rest of the existing
permitAll() list remains unchanged and that no other authorization rules are
inadvertently reordered.
---
Nitpick comments:
In `@docs/ai/features.md`:
- Line 55: The row describing "`가입 약관 동의 기록`" contains implementation-level
details (flows, cookie behavior, signupToken, endpoints `POST
/auth/google/login` and `POST /auth/google/signup`, storage of `users` and
Redis) that should be moved into a new detailed spec under docs/ai/, leaving
this main feature line as a brief pointer; create a new document (e.g.,
"auth-google-signup.md") with the full flow and storage details, replace the
long cell in the main features table with a short reference like "See
auth-google-signup.md for flow and storage details", and ensure the new doc
documents the `SIGNUP_REQUIRED` response, `signupToken` TTL, agreementsAccepted
handling, and what gets stored in users/Redis.
In `@src/test/java/com/howaboutus/backend/auth/AuthIntegrationTest.java`:
- Line 68: Replace the hardcoded magic number in the test assertion with the
constant from the token service: change the expiresInSeconds expectation in
AuthIntegrationTest to use GoogleSignupTokenService.EXPIRES_IN_SECONDS (or a
static import of it) instead of 600 so the test stays in sync with the
implementation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 01321fb0-48b4-400c-899c-97ccf1ed2191
📒 Files selected for processing (18)
docs/ai/features.mddocs/superpowers/plans/2026-06-08-google-signup-token.mddocs/superpowers/specs/2026-06-08-google-signup-token-design.mdsrc/main/java/com/howaboutus/backend/auth/controller/AuthController.javasrc/main/java/com/howaboutus/backend/auth/controller/dto/GoogleLoginRequest.javasrc/main/java/com/howaboutus/backend/auth/controller/dto/GoogleLoginResponse.javasrc/main/java/com/howaboutus/backend/auth/controller/dto/GoogleSignupRequest.javasrc/main/java/com/howaboutus/backend/auth/service/AuthService.javasrc/main/java/com/howaboutus/backend/auth/service/GoogleSignupTokenService.javasrc/main/java/com/howaboutus/backend/auth/service/dto/GoogleLoginOutcome.javasrc/main/java/com/howaboutus/backend/auth/service/dto/GoogleSignupPayload.javasrc/main/java/com/howaboutus/backend/common/config/SecurityConfig.javasrc/main/java/com/howaboutus/backend/common/error/ErrorCode.javasrc/main/java/com/howaboutus/backend/common/logging/LoggingAspect.javasrc/test/java/com/howaboutus/backend/auth/AuthIntegrationTest.javasrc/test/java/com/howaboutus/backend/auth/controller/AuthControllerTest.javasrc/test/java/com/howaboutus/backend/auth/service/AuthServiceTest.javasrc/test/java/com/howaboutus/backend/auth/service/GoogleSignupTokenServiceTest.java
|



변경 내용
POST /auth/google/login응답을GoogleLoginResponse로 타입화하고, 신규 사용자가agreementsAccepted=true를 보내지 않으면 쿠키 없이SIGNUP_REQUIRED+ 10분짜리signupToken을 반환하도록 분기 추가POST /auth/google/signup엔드포인트 신설 —signupToken+agreementsAccepted=true를 받아 신규 가입을 완료하고 인증 쿠키 발급GoogleSignupTokenService: Redis Lua 스크립트로 user → token 매핑과 token → payload 저장을 원자적으로 처리, 이전 토큰은 즉시 폐기 (TTL 10분)GoogleSignupTokenNotFound에러 코드,SecurityConfig에/auth/google/signuppermitAll 추가,LoggingAspect의 민감 파라미터 셋에signupToken등록(로그 마스킹)docs/ai/features.md"가입 약관 동의 기록" 항목 갱신 및 설계/계획 문서(docs/superpowers/specs,docs/superpowers/plans) 추가변경 이유
code는 1회용이므로 신규 사용자가 약관 미동의 상태로 로그인하면 재요청 시 같은code를 사용할 수 없어 가입 자체가 막혔다.code재사용 문제를 해소하고, 가입 임시 토큰을 Redis에 짧게 보관해 보안 노출을 최소화했다.테스트
./gradlew build/review-code-against-docs스킬로 검증AuthServiceTest,AuthControllerTest,GoogleSignupTokenServiceTest,AuthIntegrationTest에서 신규 가입 분기 / 재발급 토큰 폐기 / Redis Lua 원자성 / SIGNUP_REQUIRED 응답을 단위·통합 테스트로 검증체크리스트
하네스 변경 체크리스트
Summary by CodeRabbit
New Features
Documentation
Tests