[Refactor] 소셜로그인, 회원가입 관련 오류 수정#128
Conversation
Walkthrough이 PR은 Firebase 기반 구글 인증을 제거하고 AndroidX Credentials를 도입하여 구글 로그인 식별자 처리를 JWT Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant UI as Android UI
participant GMS as Google Sign-In (Play Services)
participant AR as AuthRepository
participant S as Server API
U->>UI: 구글 로그인 버튼 탭
UI->>GMS: Credentials API로 ID 토큰 요청
GMS-->>UI: ID Token (JWT)
UI->>AR: loginWithGoogle(idToken)
AR->>AR: JWT payload Base64(URL) 디코드
AR->>AR: JSON 파싱 후 sub 추출
AR->>S: checkNewUser(oauth2Id="google_<sub>")
S-->>AR: 결과 응답
AR-->>UI: 처리 결과 반환
UI-->>U: 로그인 결과 표시
sequenceDiagram
autonumber
actor U as User
participant VM as MyPageViewModel
participant Kakao as Kakao SDK
U->>VM: 로그아웃 확인
VM->>Kakao: logout()
alt 실패
Kakao-->>VM: 오류 콜백
VM-->>U: 오류 로그 처리
else 성공
Kakao-->>VM: 완료 콜백
VM->>VM: 구글 signOut 등 후속 처리
VM-->>U: 로그아웃 완료 UI 업데이트
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt (1)
74-80: Kakao unlink → logout 전환은 요구사항과 일치재동의 이슈 방지 목적에 부합합니다. 추가로, 필요하다면 콜백 완료까지 대기하도록
suspend래핑 후 UI 업데이트(토스트/상태 전환)를 순차 실행하는 방식도 고려해주세요.app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt (1)
100-110: 클라이언트 소문자 정규화 외에 서버 측 일관성 보장도 필요중복/대소문자 변형을 근본적으로 막으려면 서버에서
- 입력을
lower()로 정규화,- 닉네임 컬럼에 case-insensitive 유니크 인덱스 적용
을 함께 수행하세요. 클라이언트만으로는 다른 클라이언트/버전에서 일관성이 깨질 수 있습니다.app/src/main/java/com/texthip/thip/data/repository/AuthRepository.kt (1)
39-44: 파싱 유틸로 분리 + 단위 테스트 권장
sub추출 로직을 유틸로 분리하면 재사용/테스트가 수월합니다. 정상/비정상 토큰, 패딩 유무, 필드 누락 케이스를 테스트로 커버해 주세요.원하시면
parseGoogleSubFromIdToken(idToken: String): String유틸과 JUnit 테스트 스켈레톤을 드릴게요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
.gitignore(1 hunks).idea/.gitignore(0 hunks)app/build.gradle.kts(1 hunks)app/google-services.json(0 hunks)app/src/main/java/com/texthip/thip/data/repository/AuthRepository.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt(1 hunks)app/src/main/res/values/strings.xml(1 hunks)
💤 Files with no reviewable changes (2)
- .idea/.gitignore
- app/google-services.json
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt (2)
app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt (1)
onNicknameChange(40-49)app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt (1)
onNicknameChange(81-88)
🔇 Additional comments (2)
app/src/main/res/values/strings.xml (1)
448-448: Google 클라이언트 ID 교체 시 OAuth 플랫폼 일치성 확인
default_web_client_id가LoginScreen.kt77행에서 사용되고 있습니다.- 이 ID가 서버의 OAuth 2.0 Web Client와 동일 Google Cloud 프로젝트에 속하는지, Android ID 토큰의 aud 클레임과 정확히 일치하는지 확인해주세요.
build.gradle.ktsdefaultConfig의 minSdk 가 29로 설정되어 있어 Base64 디코딩(java.util.Base64) 지원에 문제가 없습니다.app/src/main/java/com/texthip/thip/data/repository/AuthRepository.kt (1)
11-14: java.util.Base64 유지프로젝트의 minSdk가 29로 API 26 이상을 보장하므로
java.util.Base64사용에 따른 런타임 문제는 없습니다. 또한java.util.Base64.getUrlEncoder().withoutPadding()/getUrlDecoder()로 JWT의 URL-safe 무패딩 인코딩·디코딩도 지원되므로,android.util.Base64로의 import 전환은 불필요합니다.Likely an incorrect or invalid review comment.
| .idea/appInsightsSettings.xml | ||
| /app/google-services.json No newline at end of file |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
google-services.json 무시 추가는 적절. 단, google-services 플러그인이 남아 있으면 빌드 실패 가능성 있음
현재 app/build.gradle.kts의 plugins 블록에 id("com.google.gms.google-services")가 남아 있습니다. Firebase 설정을 제거했다면 플러그인도 제거하세요. 파일이 없을 때 플러그인이 적용되면 빌드가 중단될 수 있습니다.
다음 스크립트로 플러그인 잔존 여부와 파일 유무를 확인하세요.
🏁 Script executed:
#!/bin/bash
echo "== google-services.json 존재 여부 =="
fd -H --type f 'google-services.json' || true
echo "== google-services 플러그인 적용 여부 =="
rg -n --glob '*gradle*' -S 'com.google.gms.google-services' || true
echo "== Firebase 종속성 잔존 여부 =="
rg -nP --glob '*gradle*' '(firebase-|platform\\(.+firebase-bom|firebase-auth)' || trueLength of output: 619
플러그인 선언 제거 필요
.gitignore에 google-services.json 무시 처리는 적절하나,
root build.gradle.kts(7행)와 app/build.gradle.kts(9행)에 남아 있는
id("com.google.gms.google-services") 선언을 삭제하세요.
🤖 Prompt for AI Agents
In .gitignore around lines 19-20 and in the Gradle files: the project is already
ignoring /app/google-services.json, so remove the plugin declarations
referencing it by deleting id("com.google.gms.google-services") from root
build.gradle.kts at line 7 and from app/build.gradle.kts at line 9; update any
related pluginManagement or classpath entries if present so the build does not
try to apply the google-services plugin while the credentials file is
intentionally ignored.
| implementation("androidx.credentials:credentials:1.2.2") | ||
| implementation("androidx.credentials:credentials-play-services-auth:1.2.2") | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
AndroidX Credentials 도입 OK. google-services 플러그인 제거 필요(중요)
app/google-services.json을 삭제/무시했는데, 상단 plugins에 id("com.google.gms.google-services")가 남아 있으면 빌드 에러가 발생할 수 있습니다. Firebase를 더 이상 사용하지 않으면 플러그인을 제거하세요.
다음으로 확인/정리하세요:
plugins블록에서com.google.gms.google-services제거- 루트/모듈 어디에도 Firebase 관련 의존성/플러그인 잔존 여부 점검(아래 스크립트 사용 가능)
참고: Credentials(1.2.2) 추가는 적절하며 play-services-auth와 병행 사용 가능합니다. 단, 로그인/로그아웃 경로를 일관되게(Identity API 또는 Credentials 기반) 정리하면 유지보수성이 좋아집니다.
🏁 Script executed:
#!/bin/bash
rg -n --glob '*gradle*' -S 'com.google.gms.google-services|firebase' || trueLength of output: 222
com.google.gms.google-services 플러그인 제거
- root
build.gradle.kts(7행)에서id("com.google.gms.google-services") version "4.4.3" apply false삭제 - app
build.gradle.kts(9행)에서id("com.google.gms.google-services")삭제 firebase키워드 기반 의존성/플러그인은 검색되지 않음
Credentials(1.2.2) 추가는 정상이며, play-services-auth 병행 사용 가능합니다.
| val payload = idToken.split('.')[1]//ID 토큰을 .기준 분리 | ||
| val decodedJson = String(Base64.getUrlDecoder().decode(payload))//디코딩 해서 JSON 문자열 반환 | ||
|
|
||
| val jsonObject = Json.parseToJsonElement(decodedJson).jsonObject | ||
| val googleSubId = jsonObject["sub"]?.jsonPrimitive?.content ?: throw IllegalStateException("구글 userID (sub)값이 없습니다.")//sub 값 추출 | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
JWT 페이로드 파싱 안전성 부족: 형식 검증·Base64URL 패딩·문자셋 누락으로 크래시 위험
idToken.split('.')[1]은 잘못된 토큰에서IndexOutOfBoundsException.java.util.Base64디코더는 JWT의 무패딩 Base64URL을 엄격하게 처리해IllegalArgumentException이 발생할 수 있습니다.String(...)이 플랫폼 기본 문자셋을 사용.
아래처럼 형식 검증, android.util.Base64 URL_SAFE + NO_PADDING/NO_WRAP, UTF-8 지정으로 교체해주세요.
- val payload = idToken.split('.')[1]//ID 토큰을 .기준 분리
- val decodedJson = String(Base64.getUrlDecoder().decode(payload))//디코딩 해서 JSON 문자열 반환
-
- val jsonObject = Json.parseToJsonElement(decodedJson).jsonObject
- val googleSubId = jsonObject["sub"]?.jsonPrimitive?.content ?: throw IllegalStateException("구글 userID (sub)값이 없습니다.")//sub 값 추출
+ val parts = idToken.split('.')
+ require(parts.size == 3) { "유효하지 않은 ID 토큰 형식입니다." }
+ val payloadB64 = parts[1]
+ val payloadBytes = android.util.Base64.decode(
+ payloadB64,
+ android.util.Base64.URL_SAFE or android.util.Base64.NO_PADDING or android.util.Base64.NO_WRAP
+ )
+ val decodedJson = payloadBytes.toString(Charsets.UTF_8)
+
+ val jsonObject = Json { ignoreUnknownKeys = true }
+ .parseToJsonElement(decodedJson).jsonObject
+ val googleSubId = jsonObject["sub"]?.jsonPrimitive?.content
+ ?: error("구글 userID(sub) 클레임이 없습니다.")🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/repository/AuthRepository.kt around
lines 39 to 44, the current payload extraction and decoding is unsafe (uses
idToken.split('.')[1], java.util.Base64 and platform default charset). Replace
with: validate the idToken has at least three dot-separated parts and throw a
clear exception if not; decode the payload using
android.util.Base64.decode(payload, Base64.URL_SAFE or-ed with Base64.NO_PADDING
| Base64.NO_WRAP) to accept JWT Base64URL without padding; convert bytes to
String using UTF-8; then parse the JSON and extract "sub", throwing a clear
IllegalStateException if missing. Ensure all potential decode/parse exceptions
are caught or allowed to surface with informative messages rather than letting
IndexOutOfBounds or IllegalArgumentException crash silently.
|
|
||
| //받아온 UID로 신규/기존 유저인지 확인 요청 | ||
| val request = AuthRequest(oauth2Id = "google_$googleUid") | ||
| val request = AuthRequest(oauth2Id = "google_$googleSubId") |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
서버 측 ID 토큰 검증(aud/iss/exp/서명) 보장 여부 확인 필요
클라이언트에서 sub만 추출해 식별자를 구성하면, 서버가 ID 토큰 서명을 검증하지 않는 경우 위·변조 위험이 있습니다. 서버가 반드시 ID 토큰 원문을 받아 OIDC 규격대로 검증하는지 확인해 주세요(특히 aud가 default_web_client_id와 일치하는지).
서버가 검증하지 않는다면, 여기서 oauth2Id만 보내지 말고 ID 토큰 원문도 함께 전달하도록 API/클라이언트 계약을 갱신하는 것을 권장합니다.
🏁 Script executed:
#!/bin/bash
# idToken(혹은 Google 토큰) 서버 전송 및 검증 유무 탐색
rg -n -C3 '\bidToken\b|GoogleIdToken|Authorization: Bearer|checkNewUser\('Length of output: 5292
ID 토큰 원문 전달 및 서버 측 검증 보장
현재 클라이언트에서 ID 토큰(idToken)을 파싱해 oauth2Id = "google_$sub" 형태로만 서버에 전송하고 있습니다.
서버가 OIDC 규격(aud/iss/exp/서명)대로 ID 토큰을 실제로 검증하는지 확인하고, 검증이 누락되어 있다면 AuthRequest에 raw idToken을 포함해 전송하도록 API/클라이언트 계약을 갱신하세요.
| containerColor = colors.DarkGrey02, | ||
| value = uiState.nickname, | ||
| onValueChange = onNicknameChange, | ||
| onValueChange = { newNickname -> onNicknameChange(newNickname.lowercase()) }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
로케일 비의존 소문자 변환 사용
lowercase()는 현재 로케일에 따라 결과가 달라질 수 있습니다. 식별자/닉네임 정규화에는 Locale.ROOT를 사용하세요.
- onValueChange = { newNickname -> onNicknameChange(newNickname.lowercase()) },
+ onValueChange = { newNickname -> onNicknameChange(newNickname.lowercase(java.util.Locale.ROOT)) },추가(파일 상단 import):
import java.util.Locale🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt
around line 117, the call using newNickname.lowercase() is locale-dependent;
change it to newNickname.lowercase(Locale.ROOT) to ensure consistent,
locale-independent normalization for identifiers/nicknames, and add the import
java.util.Locale at the top of the file.
| containerColor = colors.DarkGrey02, | ||
| value = nickname, | ||
| onValueChange = onNicknameChange, | ||
| onValueChange = { newNickname -> onNicknameChange(newNickname.lowercase()) },//소문자로 즉시 변경 |
There was a problem hiding this comment.
🛠️ Refactor suggestion
로케일 비의존 소문자 변환 사용
동일 이유로 Locale.ROOT 사용을 권장합니다.
- onValueChange = { newNickname -> onNicknameChange(newNickname.lowercase()) },//소문자로 즉시 변경
+ onValueChange = { newNickname -> onNicknameChange(newNickname.lowercase(java.util.Locale.ROOT)) }, // 소문자로 즉시 변경(로케일 비의존)추가(파일 상단 import):
import java.util.Locale🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt
around line 103, the call newNickname.lowercase() is locale-dependent; change it
to newNickname.lowercase(Locale.ROOT) and add the import java.util.Locale at the
top of the file so the nickname is lowercased in a locale-independent manner.
➕ 이슈 링크
🔎 작업 내용
카카오 유저가 로그아웃 후 재로그인을 시도하는 경우
기존 유저임에도 카카오 측에서 사용자에게 권한 동의 등을 얻는 상황이 발생했습니다. 해당 원인 파악 후, unlink 가 아닌 logout으로 처리하도록 수정했습니다
카카오 디벨로퍼스 계정을 팀 통합 계정으로 변경 했습니다.
구글 유저가 안드로이드로 로그인하는 경우/ 웹으로 로그인 하는 경우
이 두 경우를 각각 다른 유저로 처리해버리는 오류가 있었습니다.
이는 안드와 웹의 로그인 처리 방식이 달라, provider_id의 형식이 다르게 서버에 전송되어 발생하는 오류였습니다.
firebase에서 제공해주는 uid 를 사용하는 기존 방식에서 구글에서 발급해주는 sub 변수에 들어있는 uid를 사용하도록 변경하여 해결했습니다.
회원가입 시 닉네임 입력의 경우, 대문자가 포함되는 것은 오류로 인식하는데, 입력은 허용되어 있는 오류
사용자가 대문자 입력 시, 바로 소문자로 변경되어 입력되도록 처리했습니다.
닉네임 변경 시에도 동일하게 처리했습니다.
📸 스크린샷
😢 해결하지 못한 과제
📢 리뷰어들에게
Summary by CodeRabbit