-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from soma-baekgu/BG-133-link-oauth
[BG-133]: 구글 OAuth2 연동하기 (6.5h / 5h)
- Loading branch information
Showing
20 changed files
with
417 additions
and
4 deletions.
There are no files selected for viewing
This file contains 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
40 changes: 40 additions & 0 deletions
40
api/src/main/kotlin/com/backgu/amaker/auth/config/AuthConfig.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.backgu.amaker.auth.config | ||
|
||
import org.springframework.beans.factory.annotation.Value | ||
import org.springframework.context.annotation.Configuration | ||
|
||
@Configuration | ||
class AuthConfig { | ||
@Value("\${oauth.google.client-id}") | ||
lateinit var clientId: String | ||
|
||
@Value("\${oauth.google.client-secret}") | ||
lateinit var clientSecret: String | ||
|
||
@Value("\${oauth.google.redirect-uri}") | ||
lateinit var redirectUri: String | ||
|
||
@Value("\${oauth.google.client-name}") | ||
lateinit var clientName: String | ||
|
||
@Value("\${oauth.google.base-url}") | ||
lateinit var baseUrl: String | ||
|
||
@Value("\${oauth.google.scope}") | ||
lateinit var scope: String | ||
|
||
@Value("\${oauth.google.oauth-url}") | ||
lateinit var oauthUrl: String | ||
|
||
@Value("\${oauth.google.api-url}") | ||
lateinit var apiUrl: String | ||
|
||
var grantType = "authorization_code" | ||
|
||
fun oauthUrl(): String = | ||
baseUrl + | ||
"?client_id=$clientId" + | ||
"&redirect_uri=${java.net.URLEncoder.encode(redirectUri, "UTF-8")}" + | ||
"&response_type=code" + | ||
"&scope=${scope.replace(",", "%20")}" | ||
} |
32 changes: 32 additions & 0 deletions
32
api/src/main/kotlin/com/backgu/amaker/auth/controller/AuthController.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.backgu.amaker.auth.controller | ||
|
||
import com.backgu.amaker.auth.config.AuthConfig | ||
import com.backgu.amaker.auth.service.AuthService | ||
import jakarta.servlet.http.HttpServletResponse | ||
import org.springframework.stereotype.Controller | ||
import org.springframework.web.bind.annotation.GetMapping | ||
import org.springframework.web.bind.annotation.RequestMapping | ||
import org.springframework.web.bind.annotation.RequestParam | ||
|
||
@Controller | ||
@RequestMapping("/auth") | ||
class AuthController( | ||
val authConfig: AuthConfig, | ||
val authService: AuthService, | ||
) { | ||
@GetMapping("/oauth/google") | ||
fun googleAuth(response: HttpServletResponse) { | ||
response.sendRedirect(authConfig.oauthUrl()) | ||
} | ||
|
||
@GetMapping("/code/google") | ||
fun login( | ||
@RequestParam(name = "code") authorizationCode: String, | ||
@RequestParam(name = "scope", required = false) scope: String, | ||
@RequestParam(name = "authuser", required = false) authUser: String, | ||
@RequestParam(name = "prompt", required = false) prompt: String, | ||
): String { | ||
authService.googleLogin(authorizationCode) | ||
return "redirect:/" | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
api/src/main/kotlin/com/backgu/amaker/auth/dto/GoogleOAuth2AccessTokenDto.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.backgu.amaker.auth.dto | ||
|
||
import com.fasterxml.jackson.databind.PropertyNamingStrategies | ||
import com.fasterxml.jackson.databind.annotation.JsonNaming | ||
|
||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) | ||
class GoogleOAuth2AccessTokenDto( | ||
val accessToken: String?, | ||
val expiresIn: Int?, | ||
val idToken: String?, | ||
val scope: String?, | ||
val tokenType: String?, | ||
) { | ||
fun getBearerToken(): String = "Bearer $accessToken" | ||
} |
14 changes: 14 additions & 0 deletions
14
api/src/main/kotlin/com/backgu/amaker/auth/dto/GoogleUserInfoDto.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.backgu.amaker.auth.dto | ||
|
||
import com.fasterxml.jackson.databind.PropertyNamingStrategies | ||
import com.fasterxml.jackson.databind.annotation.JsonNaming | ||
|
||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) | ||
class GoogleUserInfoDto( | ||
val id: String?, | ||
val email: String?, | ||
val verifiedEmail: Boolean?, | ||
val name: String?, | ||
val givenName: String?, | ||
val picture: String?, | ||
) |
14 changes: 14 additions & 0 deletions
14
api/src/main/kotlin/com/backgu/amaker/auth/infra/GoogleApiClient.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.backgu.amaker.auth.infra | ||
|
||
import com.backgu.amaker.auth.dto.GoogleUserInfoDto | ||
import com.backgu.amaker.config.CaughtHttpExchange | ||
import org.springframework.web.bind.annotation.RequestHeader | ||
import org.springframework.web.service.annotation.GetExchange | ||
|
||
@CaughtHttpExchange | ||
interface GoogleApiClient { | ||
@GetExchange("/oauth2/v2/userinfo") | ||
fun getUserInfo( | ||
@RequestHeader("Authorization") authorization: String, | ||
): GoogleUserInfoDto? | ||
} |
19 changes: 19 additions & 0 deletions
19
api/src/main/kotlin/com/backgu/amaker/auth/infra/GoogleOAuthClient.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.backgu.amaker.auth.infra | ||
|
||
import com.backgu.amaker.auth.dto.GoogleOAuth2AccessTokenDto | ||
import com.backgu.amaker.config.CaughtHttpExchange | ||
import org.springframework.http.MediaType | ||
import org.springframework.web.bind.annotation.RequestParam | ||
import org.springframework.web.service.annotation.PostExchange | ||
|
||
@CaughtHttpExchange | ||
interface GoogleOAuthClient { | ||
@PostExchange("/token", contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | ||
fun getGoogleOAuth2( | ||
@RequestParam("code") authCode: String, | ||
@RequestParam("redirect_uri") redirectUri: String, | ||
@RequestParam("grant_type") grantType: String, | ||
@RequestParam("client_secret") clientSecret: String, | ||
@RequestParam("client_id") clientId: String, | ||
): GoogleOAuth2AccessTokenDto? | ||
} |
33 changes: 33 additions & 0 deletions
33
api/src/main/kotlin/com/backgu/amaker/auth/service/AuthService.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.backgu.amaker.auth.service | ||
|
||
import com.backgu.amaker.auth.config.AuthConfig | ||
import com.backgu.amaker.auth.dto.GoogleOAuth2AccessTokenDto | ||
import com.backgu.amaker.auth.dto.GoogleUserInfoDto | ||
import com.backgu.amaker.auth.infra.GoogleApiClient | ||
import com.backgu.amaker.auth.infra.GoogleOAuthClient | ||
import org.springframework.stereotype.Service | ||
import java.lang.IllegalArgumentException | ||
|
||
@Service | ||
class AuthService( | ||
val googleOAuthClient: GoogleOAuthClient, | ||
val googleApiClient: GoogleApiClient, | ||
val authConfig: AuthConfig, | ||
) { | ||
fun googleLogin(authorizationCode: String): String? { | ||
val accessTokenDto: GoogleOAuth2AccessTokenDto = | ||
googleOAuthClient.getGoogleOAuth2( | ||
authorizationCode, | ||
authConfig.redirectUri, | ||
authConfig.grantType, | ||
authConfig.clientSecret, | ||
authConfig.clientId, | ||
) ?: throw IllegalArgumentException("Failed to get access token") | ||
|
||
val userInfo: GoogleUserInfoDto = | ||
googleApiClient.getUserInfo(accessTokenDto.getBearerToken()) | ||
?: throw IllegalArgumentException("Failed to get user information") | ||
|
||
return userInfo.email | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.backgu.amaker.config | ||
|
||
import org.springframework.context.annotation.Configuration | ||
import org.springframework.context.annotation.EnableAspectJAutoProxy | ||
|
||
@Configuration | ||
@EnableAspectJAutoProxy | ||
class AppConfig |
8 changes: 8 additions & 0 deletions
8
api/src/main/kotlin/com/backgu/amaker/config/CaughtHttpExchange.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.backgu.amaker.config | ||
|
||
import org.springframework.web.service.annotation.HttpExchange | ||
|
||
@HttpExchange | ||
@Target(AnnotationTarget.CLASS) | ||
@Retention(AnnotationRetention.RUNTIME) | ||
annotation class CaughtHttpExchange |
23 changes: 23 additions & 0 deletions
23
api/src/main/kotlin/com/backgu/amaker/config/RestClientAspect.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.backgu.amaker.config | ||
|
||
import org.aspectj.lang.ProceedingJoinPoint | ||
import org.aspectj.lang.annotation.Around | ||
import org.aspectj.lang.annotation.Aspect | ||
import org.aspectj.lang.annotation.Pointcut | ||
import org.springframework.stereotype.Component | ||
|
||
@Aspect | ||
@Component | ||
class RestClientAspect { | ||
@Pointcut("within(@CaughtHttpExchange *)") | ||
fun caughtHttpExchange() { | ||
} | ||
|
||
@Around("caughtHttpExchange()") | ||
fun handleException(jointPoint: ProceedingJoinPoint): Any? = | ||
try { | ||
jointPoint.proceed() | ||
} catch (e: Exception) { | ||
null | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
api/src/main/kotlin/com/backgu/amaker/config/RestClientConfig.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.backgu.amaker.config | ||
|
||
import com.backgu.amaker.auth.config.AuthConfig | ||
import com.backgu.amaker.auth.infra.GoogleApiClient | ||
import com.backgu.amaker.auth.infra.GoogleOAuthClient | ||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.Configuration | ||
import org.springframework.web.client.RestClient | ||
import org.springframework.web.client.support.RestClientAdapter | ||
import org.springframework.web.service.invoker.HttpServiceProxyFactory | ||
|
||
@Configuration | ||
class RestClientConfig( | ||
val authConfig: AuthConfig, | ||
) { | ||
@Bean | ||
fun googleOauth2Service(): GoogleOAuthClient { | ||
val restClient = RestClient.builder().baseUrl(authConfig.oauthUrl).build() | ||
val adapter = RestClientAdapter.create(restClient) | ||
val factory = HttpServiceProxyFactory.builderFor(adapter).build() | ||
|
||
return factory.createClient(GoogleOAuthClient::class.java) | ||
} | ||
|
||
@Bean | ||
fun googleApiService(): GoogleApiClient { | ||
val restClient = RestClient.builder().baseUrl(authConfig.apiUrl).build() | ||
val adapter = RestClientAdapter.create(restClient) | ||
val factory = HttpServiceProxyFactory.builderFor(adapter).build() | ||
|
||
return factory.createClient(GoogleApiClient::class.java) | ||
} | ||
} |
This file contains 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
67 changes: 67 additions & 0 deletions
67
api/src/test/kotlin/com/backgu/amaker/auth/service/AuthServiceTest.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.backgu.amaker.auth.service | ||
|
||
import com.backgu.amaker.auth.infra.GoogleApiClient | ||
import com.backgu.amaker.auth.infra.GoogleOAuthClient | ||
import com.backgu.amaker.auth.test.FailedFakeGoogleApiClient | ||
import com.backgu.amaker.auth.test.FailedFakeGoogleOAuthClient | ||
import com.backgu.amaker.auth.test.SuccessfulStubGoogleApiClient | ||
import com.backgu.amaker.auth.test.SuccessfulStubGoogleOAuthClient | ||
import com.backgu.amaker.fixture.AuthFixture | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.assertj.core.api.Assertions.assertThatThrownBy | ||
import org.junit.jupiter.api.DisplayName | ||
import org.junit.jupiter.api.Test | ||
|
||
class AuthServiceTest { | ||
private lateinit var authService: AuthService | ||
private lateinit var googleOAuthClient: GoogleOAuthClient | ||
private lateinit var googleApiClient: GoogleApiClient | ||
|
||
@Test | ||
@DisplayName("구글 성공 로그인 테스트") | ||
fun successfulGoogleLoginTest() { | ||
// given | ||
val email = "[email protected]" | ||
googleOAuthClient = SuccessfulStubGoogleOAuthClient() | ||
googleApiClient = SuccessfulStubGoogleApiClient(email) | ||
authService = AuthService(googleOAuthClient, googleApiClient, AuthFixture.createUserRequest()) | ||
|
||
// when | ||
val result = authService.googleLogin("authCode") | ||
|
||
// then | ||
assertThat(result).isEqualTo(email) | ||
} | ||
|
||
@Test | ||
@DisplayName("구글 oauth 서버에서 토큰 획득 실패 테스트") | ||
fun failedToGetAccessTokenTest() { | ||
// given | ||
val email = "[email protected]" | ||
googleOAuthClient = FailedFakeGoogleOAuthClient() | ||
googleApiClient = SuccessfulStubGoogleApiClient(email) | ||
authService = AuthService(googleOAuthClient, googleApiClient, AuthFixture.createUserRequest()) | ||
|
||
// when | ||
// then | ||
assertThatThrownBy { authService.googleLogin("authCode") } | ||
.isInstanceOf(IllegalArgumentException::class.java) | ||
.hasMessage("Failed to get access token") | ||
} | ||
|
||
@Test | ||
@DisplayName("구글 oauth 서버에서 토큰 획득 실패 테스트") | ||
fun failedToUserInfo() { | ||
// given | ||
val email = "[email protected]" | ||
googleOAuthClient = SuccessfulStubGoogleOAuthClient() | ||
googleApiClient = FailedFakeGoogleApiClient() | ||
authService = AuthService(googleOAuthClient, googleApiClient, AuthFixture.createUserRequest()) | ||
|
||
// when | ||
// then | ||
assertThatThrownBy { authService.googleLogin("authCode") } | ||
.isInstanceOf(IllegalArgumentException::class.java) | ||
.hasMessage("Failed to get user information") | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
api/src/test/kotlin/com/backgu/amaker/auth/test/FailedFakeGoogleApiClient.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.backgu.amaker.auth.test | ||
|
||
import com.backgu.amaker.auth.dto.GoogleUserInfoDto | ||
import com.backgu.amaker.auth.infra.GoogleApiClient | ||
|
||
class FailedFakeGoogleApiClient : GoogleApiClient { | ||
override fun getUserInfo(authorization: String): GoogleUserInfoDto? = throw IllegalArgumentException("Failed to get user information") | ||
} |
16 changes: 16 additions & 0 deletions
16
api/src/test/kotlin/com/backgu/amaker/auth/test/FailedFakeGoogleOAuthClient.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.backgu.amaker.auth.test | ||
|
||
import com.backgu.amaker.auth.dto.GoogleOAuth2AccessTokenDto | ||
import com.backgu.amaker.auth.infra.GoogleOAuthClient | ||
|
||
class FailedFakeGoogleOAuthClient : GoogleOAuthClient { | ||
override fun getGoogleOAuth2( | ||
authCode: String, | ||
redirectUri: String, | ||
grantType: String, | ||
clientSecret: String, | ||
clientId: String, | ||
): GoogleOAuth2AccessTokenDto? { | ||
throw IllegalArgumentException("Failed to get access token") | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
api/src/test/kotlin/com/backgu/amaker/auth/test/SuccessfulStubGoogleApiClient.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.backgu.amaker.auth.test | ||
|
||
import com.backgu.amaker.auth.dto.GoogleUserInfoDto | ||
import com.backgu.amaker.auth.infra.GoogleApiClient | ||
|
||
class SuccessfulStubGoogleApiClient( | ||
email: String, | ||
) : GoogleApiClient { | ||
private val googleUserInfo: GoogleUserInfoDto = | ||
GoogleUserInfoDto( | ||
id = "stubId", | ||
email = email, | ||
verifiedEmail = true, | ||
name = "stubName", | ||
givenName = "stubGivenName", | ||
picture = "stubPicture", | ||
) | ||
|
||
override fun getUserInfo(authorization: String): GoogleUserInfoDto = googleUserInfo | ||
} |
Oops, something went wrong.