Skip to content

Commit 1fff654

Browse files
committed
[BOOK-469] feat: apis,infra google social login (#141)
1 parent 1167e92 commit 1fff654

File tree

9 files changed

+193
-31
lines changed

9 files changed

+193
-31
lines changed

apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank
55
import org.yapp.apis.auth.exception.AuthErrorCode
66
import org.yapp.apis.auth.exception.AuthException
77
import org.yapp.apis.auth.strategy.signin.AppleAuthCredentials
8+
import org.yapp.apis.auth.strategy.signin.GoogleAuthCredentials
89
import org.yapp.apis.auth.strategy.signin.KakaoAuthCredentials
910
import org.yapp.apis.auth.strategy.signin.SignInCredentials
1011
import org.yapp.domain.user.ProviderType
@@ -61,6 +62,8 @@ data class SocialLoginRequest private constructor(
6162
)
6263
AppleAuthCredentials(request.validOauthToken(), authCode)
6364
}
65+
66+
ProviderType.GOOGLE -> GoogleAuthCredentials(request.validOauthToken())
6467
}
6568
}
6669
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.yapp.apis.auth.manager
2+
3+
import org.springframework.beans.factory.annotation.Value
4+
import org.springframework.stereotype.Component
5+
import org.springframework.web.client.RestClient
6+
import org.yapp.infra.external.oauth.google.response.GoogleUserInfo
7+
8+
@Component
9+
class GoogleApiManager(
10+
@Value("\${oauth.google.url.user-info}") private val userInfoUrl: String,
11+
private val restClient: RestClient,
12+
) {
13+
fun getUserInfo(accessToken: String): GoogleUserInfo {
14+
return restClient.get()
15+
.uri(userInfoUrl)
16+
.headers { it.setBearerAuth(accessToken) }
17+
.retrieve()
18+
.body(GoogleUserInfo::class.java)!!
19+
}
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.yapp.apis.auth.strategy.signin
2+
3+
import org.yapp.domain.user.ProviderType
4+
5+
data class GoogleAuthCredentials(
6+
val accessToken: String,
7+
) : SignInCredentials() {
8+
override fun getProviderType(): ProviderType {
9+
return ProviderType.GOOGLE
10+
}
11+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package org.yapp.apis.auth.strategy.signin
2+
3+
import mu.KotlinLogging
4+
import org.springframework.stereotype.Component
5+
import org.yapp.apis.auth.dto.response.UserCreateInfoResponse
6+
import org.yapp.apis.auth.exception.AuthErrorCode
7+
import org.yapp.apis.auth.exception.AuthException
8+
import org.yapp.apis.auth.manager.GoogleApiManager
9+
import org.yapp.apis.auth.util.NicknameGenerator
10+
import org.yapp.domain.user.ProviderType
11+
import org.yapp.infra.external.oauth.google.response.GoogleUserInfo
12+
13+
@Component
14+
class GoogleSignInStrategy(
15+
private val googleApiManager: GoogleApiManager
16+
) : SignInStrategy {
17+
18+
private val log = KotlinLogging.logger {}
19+
20+
override fun getProviderType(): ProviderType = ProviderType.GOOGLE
21+
22+
override fun authenticate(credentials: SignInCredentials): UserCreateInfoResponse {
23+
return try {
24+
val googleCredentials = validateCredentials(credentials)
25+
val googleUser = googleApiManager.getUserInfo(googleCredentials.accessToken)
26+
createUserInfo(googleUser)
27+
} catch (exception: Exception) {
28+
log.error("Google authentication failed", exception)
29+
when (exception) {
30+
is AuthException -> throw exception
31+
else -> throw AuthException(AuthErrorCode.FAILED_TO_GET_USER_INFO, exception.message)
32+
}
33+
}
34+
}
35+
36+
private fun validateCredentials(credentials: SignInCredentials): GoogleAuthCredentials {
37+
return credentials as? GoogleAuthCredentials
38+
?: throw AuthException(
39+
AuthErrorCode.INVALID_CREDENTIALS,
40+
"Credentials must be GoogleAuthCredentials"
41+
)
42+
}
43+
44+
private fun createUserInfo(googleUser: GoogleUserInfo): UserCreateInfoResponse {
45+
return UserCreateInfoResponse.of(
46+
email = googleUser.email ?: ("google_${googleUser.id}@google.com"),
47+
nickname = NicknameGenerator.generate(),
48+
profileImageUrl = googleUser.picture,
49+
providerType = ProviderType.GOOGLE,
50+
providerId = googleUser.id
51+
)
52+
}
53+
}

apis/src/main/resources/application.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ swagger:
6161
description: YAPP API Documentation for Development
6262
version: v1.0.0-dev
6363

64+
oauth:
65+
google:
66+
url:
67+
user-info: https://www.googleapis.com/oauth2/v2/userinfo
68+
6469
---
6570
spring:
6671
config:

apis/src/main/resources/static/kakao-login.html

Lines changed: 85 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,38 +53,50 @@
5353
color: #000;
5454
}
5555

56-
.apple-btn {
57-
background-color: #000;
58-
color: #fff;
59-
}
60-
</style>
61-
</head>
62-
<body>
63-
64-
<h1 style="text-align:center">소셜 로그인 테스트</h1>
65-
66-
<div class="tabs">
67-
<div class="tab active" data-tab="kakao">카카오 로그인</div>
68-
<div class="tab" data-tab="apple">애플 로그인</div>
69-
</div>
70-
71-
<div class="tab-content active" id="kakao">
72-
<p style="text-align:center;">카카오 계정으로 로그인하려면 아래 버튼을 클릭하세요.</p>
73-
<div style="text-align:center;">
74-
<button id="kakao-login-btn" class="btn kakao-btn">카카오 로그인</button>
75-
</div>
76-
</div>
77-
78-
<div class="tab-content" id="apple">
79-
<p style="text-align:center;">애플 계정으로 로그인하려면 아래 버튼을 클릭하세요.</p>
80-
<div style="text-align:center;">
81-
<button id="apple-login-btn" class="btn apple-btn">Apple 로그인</button>
82-
</div>
83-
</div>
84-
56+
.apple-btn {
57+
background-color: #000;
58+
color: #fff;
59+
}
60+
.google-btn {
61+
background-color: #4285F4;
62+
color: #fff;
63+
}
64+
</style>
65+
</head>
66+
<body>
67+
68+
<h1 style="text-align:center">소셜 로그인 테스트</h1>
69+
70+
<div class="tabs">
71+
<div class="tab active" data-tab="kakao">카카오 로그인</div>
72+
<div class="tab" data-tab="apple">애플 로그인</div>
73+
<div class="tab" data-tab="google">구글 로그인</div>
74+
</div>
75+
76+
<div class="tab-content active" id="kakao">
77+
<p style="text-align:center;">카카오 계정으로 로그인하려면 아래 버튼을 클릭하세요.</p>
78+
<div style="text-align:center;">
79+
<button id="kakao-login-btn" class="btn kakao-btn">카카오 로그인</button>
80+
</div>
81+
</div>
82+
83+
<div class="tab-content" id="apple">
84+
<p style="text-align:center;">애플 계정으로 로그인하려면 아래 버튼을 클릭하세요.</p>
85+
<div style="text-align:center;">
86+
<button id="apple-login-btn" class="btn apple-btn">Apple 로그인</button>
87+
</div>
88+
</div>
89+
90+
<div class="tab-content" id="google">
91+
<p style="text-align:center;">구글 계정으로 로그인하려면 아래 버튼을 클릭하세요.</p>
92+
<div style="text-align:center;">
93+
<button id="google-login-btn" class="btn google-btn">Google 로그인</button>
94+
</div>
95+
</div>
8596
<!-- 결과 출력 -->
8697
<pre id="result" style="margin-top: 30px; white-space: pre-wrap;"></pre>
8798

99+
<meta name="google-signin-client_id" content="">
88100
<!-- Kakao SDK -->
89101
<script src="https://developers.kakao.com/sdk/js/kakao.js"></script>
90102

@@ -93,6 +105,26 @@ <h1 style="text-align:center">소셜 로그인 테스트</h1>
93105
src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
94106

95107
<script>
108+
// Google Sign-In Callback (Global function)
109+
function onSignIn(googleUser) {
110+
var id_token = googleUser.getAuthResponse().id_token;
111+
fetch(`${API_SERVER}/api/v1/auth/signin`, {
112+
method: 'POST',
113+
headers: {'Content-Type': 'application/json'},
114+
body: JSON.stringify({
115+
providerType: "GOOGLE",
116+
oauthToken: id_token
117+
})
118+
})
119+
.then(res => res.json())
120+
.then(data => {
121+
document.getElementById('result').textContent = 'Google 로그인 성공\n\nJWT: ' + data.accessToken;
122+
})
123+
.catch(err => {
124+
document.getElementById('result').textContent = 'Google 로그인 실패: ' + err.message;
125+
});
126+
}
127+
96128
// 탭 전환
97129
const tabs = document.querySelectorAll('.tab');
98130
const contents = document.querySelectorAll('.tab-content');
@@ -238,7 +270,30 @@ <h1 style="text-align:center">소셜 로그인 테스트</h1>
238270
});
239271
}
240272
});
241-
</script>
242273

274+
// Google Sign-In Initialization
275+
var GoogleAuth; // GoogleAuth object
276+
277+
function initGoogleAuth() {
278+
gapi.client.init({
279+
clientId: document.querySelector('meta[name="google-signin-client_id"]').content,
280+
scope: 'profile email'
281+
}).then(function () {
282+
GoogleAuth = gapi.auth2.getAuthInstance();
283+
// Attach the click listener to the Google login button
284+
document.getElementById('google-login-btn').addEventListener('click', () => {
285+
GoogleAuth.signIn().then(onSignIn, (error) => {
286+
console.error('Google Sign-In failed:', error);
287+
document.getElementById('result').textContent = 'Google 로그인 실패: ' + JSON.stringify(error);
288+
});
289+
});
290+
});
291+
}
292+
293+
function handleGoogleClientLoad() {
294+
gapi.load('client:auth2', initGoogleAuth);
295+
}
296+
</script>
297+
<script async defer src="https://apis.google.com/js/api.js" onload="this.onload=function(){};handleGoogleClientLoad();"></script>
243298
</body>
244299
</html>

domain/src/main/kotlin/org/yapp/domain/user/ProviderType.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ package org.yapp.domain.user
44
* Enum representing different authentication providers.
55
*/
66
enum class ProviderType {
7-
KAKAO, APPLE
7+
KAKAO, APPLE, GOOGLE
88
}

infra/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ dependencies {
88
implementation(Dependencies.Spring.BOOT_STARTER_DATA_REDIS)
99
implementation(Dependencies.Spring.KOTLIN_REFLECT)
1010

11+
12+
implementation(Dependencies.Spring.BOOT_STARTER_OAUTH2_CLIENT)
13+
1114
implementation(Dependencies.RestClient.HTTP_CLIENT5)
1215
implementation(Dependencies.RestClient.HTTP_CORE5)
1316

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.yapp.infra.external.oauth.google.response
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
5+
data class GoogleUserInfo(
6+
@JsonProperty("id")
7+
val id: String,
8+
@JsonProperty("email")
9+
val email: String?,
10+
@JsonProperty("picture")
11+
val picture: String?,
12+
)

0 commit comments

Comments
 (0)