diff --git a/api/src/main/kotlin/com/oksusu/susu/api/auth/application/AuthFacade.kt b/api/src/main/kotlin/com/oksusu/susu/api/auth/application/AuthFacade.kt index a70a302d..c6684c02 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/auth/application/AuthFacade.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/auth/application/AuthFacade.kt @@ -1,11 +1,6 @@ package com.oksusu.susu.api.auth.application -import com.oksusu.susu.api.auth.model.AdminUserImpl -import com.oksusu.susu.api.auth.model.AuthContextImpl -import com.oksusu.susu.api.auth.model.AuthUser -import com.oksusu.susu.api.auth.model.AuthUserImpl -import com.oksusu.susu.api.auth.model.AuthUserToken -import com.oksusu.susu.api.auth.model.TokenDto +import com.oksusu.susu.api.auth.model.* import com.oksusu.susu.api.auth.model.response.TokenRefreshRequest import com.oksusu.susu.api.event.model.CreateUserStatusHistoryEvent import com.oksusu.susu.api.event.model.CreateUserWithdrawEvent @@ -139,7 +134,7 @@ class AuthFacade( } @Transactional - suspend fun withdraw(authUser: AuthUser, code: String?, accessToken: String?) { + suspend fun withdraw(authUser: AuthUser, code: String?, googleAccessToken: String?, appleAccessToken: String?) { val (deactivatedPosts, userAndUserStatusModel) = parZipWithMDC( { postService.findAllByUid(authUser.uid) }, { userService.getUserAndUserStatus(authUser.uid) } @@ -191,7 +186,7 @@ class AuthFacade( } val oAuthDeferred = async { - oAuthService.withdraw(user.oauthInfo, code, accessToken) + oAuthService.withdraw(user.oauthInfo, code, googleAccessToken, appleAccessToken) } awaitAll(txDeferred, oAuthDeferred) diff --git a/api/src/main/kotlin/com/oksusu/susu/api/auth/application/OAuthService.kt b/api/src/main/kotlin/com/oksusu/susu/api/auth/application/OAuthService.kt index f53e4ec6..271ef2f9 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/auth/application/OAuthService.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/auth/application/OAuthService.kt @@ -52,11 +52,11 @@ class OAuthService( } /** oauth 유저 회원 탈퇴하기 */ - suspend fun withdraw(oauthInfo: OauthInfo, code: String?, accessToken: String?) { + suspend fun withdraw(oauthInfo: OauthInfo, code: String?, googleAccessToken: String?, appleAccessToken: String?) { when (oauthInfo.oAuthProvider) { OAuthProvider.KAKAO -> kakaoOAuthService.withdraw(oauthInfo.oAuthId) - OAuthProvider.APPLE -> appleOAuthService.withdraw(code!!) - OAuthProvider.GOOGLE -> googleOAuthService.withdraw(accessToken!!) + OAuthProvider.APPLE -> appleOAuthService.withdraw(appleAccessToken!!) + OAuthProvider.GOOGLE -> googleOAuthService.withdraw(googleAccessToken!!) } } } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/auth/application/oauth/AppleOAuthService.kt b/api/src/main/kotlin/com/oksusu/susu/api/auth/application/oauth/AppleOAuthService.kt index b42925cf..7ea3f17c 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/auth/application/oauth/AppleOAuthService.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/auth/application/oauth/AppleOAuthService.kt @@ -39,12 +39,12 @@ class AppleOAuthService( /** link */ suspend fun getOAuthLoginLinkDev(): OAuthLoginLinkResponse { - val redirectUrl = domainName + appleOAuthUrlConfig.webCallbackUrl + val redirectUrl = domainName + appleOAuthUrlConfig.devCallbackUrl return OAuthLoginLinkResponse( appleOAuthUrlConfig.appleIdUrl + String.format( appleOAuthUrlConfig.authorizeUrl, - appleOAuthSecretConfig.webClientId, + appleOAuthSecretConfig.clientId, redirectUrl ) ) @@ -64,7 +64,7 @@ class AppleOAuthService( /** oauth token 받아오기 */ suspend fun getOAuthTokenDev(code: String): OAuthTokenResponse { - val redirectUrl = domainName + appleOAuthUrlConfig.redirectUrl + val redirectUrl = domainName + appleOAuthUrlConfig.devCallbackUrl return getAppleToken(redirectUrl, code, appleOAuthSecretConfig.clientId, getClientSecretDev()) } @@ -83,8 +83,6 @@ class AppleOAuthService( appleClient.getToken(redirectUrl, code, clientId, clientSecret) } - getOIDCDecodePayload(tokens.idToken) - return OAuthTokenResponse.fromApple(tokens) } @@ -96,11 +94,6 @@ class AppleOAuthService( return OAuthUserInfoDto.fromApple(oidcDecodePayload.sub) } - suspend fun getAppleOAuthInfoDev(idToken: String): OAuthUserInfoDto { - val oidcDecodePayload = getOIDCDecodePayloadDev(idToken) - return OAuthUserInfoDto.fromApple(oidcDecodePayload.sub) - } - /** * oidc decode */ @@ -114,24 +107,12 @@ class AppleOAuthService( ) } - private suspend fun getOIDCDecodePayloadDev(token: String): OidcDecodePayload { - val oidcPublicKeysResponse = oidcService.getOidcPublicKeys(OAuthProvider.APPLE) - return oidcService.getPayloadFromIdToken( - token, - appleOAuthUrlConfig.appleIdUrl, - appleOAuthSecretConfig.webClientId, - oidcPublicKeysResponse - ) - } - /** 회원 탈퇴합니다 */ - suspend fun withdraw(code: String) { - val tokens = getOAuthWithdrawToken(code) - + suspend fun withdraw(accessToken: String) { withMDCContext(Dispatchers.IO) { appleClient.withdraw( appleOAuthSecretConfig.clientId, - tokens.accessToken, + accessToken, getClientSecret() ) } @@ -145,19 +126,19 @@ class AppleOAuthService( } private fun getClientSecretDev(): String { - return createClientSecret(appleOAuthSecretConfig.webClientId) + return createClientSecret(appleOAuthSecretConfig.clientId) } private fun createClientSecret(clientId: String): String { - val iat = Date().toInstant() - val exp = Date(iat.epochSecond + 3600000).toInstant() + val iat = Date() + val exp = Date(iat.time + 3600000).toInstant() val headers = mapOf("alg" to "ES256", "kid" to appleOAuthSecretConfig.keyId) val unsignedJWT = JWT.create().apply { this.withHeader(headers) this.withIssuer(appleOAuthSecretConfig.teamId) - this.withAudience(appleOAuthSecretConfig.authKey) + this.withAudience(appleOAuthUrlConfig.appleIdUrl) this.withIssuedAt(iat) this.withExpiresAt(exp) this.withSubject(clientId) diff --git a/api/src/main/kotlin/com/oksusu/susu/api/auth/application/oauth/OidcService.kt b/api/src/main/kotlin/com/oksusu/susu/api/auth/application/oauth/OidcService.kt index d58d7fe6..600870a1 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/auth/application/oauth/OidcService.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/auth/application/oauth/OidcService.kt @@ -95,8 +95,7 @@ class OidcService( return OidcDecodePayload( iss = jwt.getClaim("iss").asString(), aud = jwt.getClaim("aud").asString(), - sub = jwt.getClaim("sub").asString(), - email = jwt.getClaim("email").asString() + sub = jwt.getClaim("sub").asString() ) } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/auth/model/OidcDecodePayload.kt b/api/src/main/kotlin/com/oksusu/susu/api/auth/model/OidcDecodePayload.kt index 3382dc0e..84cdd39b 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/auth/model/OidcDecodePayload.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/auth/model/OidcDecodePayload.kt @@ -7,5 +7,4 @@ data class OidcDecodePayload( val aud: String, /** oauth provider account unique id */ val sub: String, - val email: String, ) diff --git a/api/src/main/kotlin/com/oksusu/susu/api/auth/model/response/OAuthTokenResponse.kt b/api/src/main/kotlin/com/oksusu/susu/api/auth/model/response/OAuthTokenResponse.kt index 4f6d8103..228a8114 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/auth/model/response/OAuthTokenResponse.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/auth/model/response/OAuthTokenResponse.kt @@ -9,6 +9,8 @@ data class OAuthTokenResponse( val accessToken: String, /** oauth refresh token */ val refreshToken: String, + /** oauth id token (apple은 이거 사용) */ + val idToken: String? = null, ) { companion object { fun fromKakao(kakaoOAuthTokenResponse: KakaoOAuthTokenResponse): OAuthTokenResponse { @@ -21,7 +23,8 @@ data class OAuthTokenResponse( fun fromApple(appleOAuthTokenResponse: AppleOAuthTokenResponse): OAuthTokenResponse { return OAuthTokenResponse( accessToken = appleOAuthTokenResponse.accessToken, - refreshToken = appleOAuthTokenResponse.refreshToken + refreshToken = appleOAuthTokenResponse.refreshToken, + idToken = appleOAuthTokenResponse.idToken ) } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/AuthResource.kt b/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/AuthResource.kt index 42a80329..d304aef8 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/AuthResource.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/AuthResource.kt @@ -35,12 +35,14 @@ class AuthResource( * 회원 탈퇴 * kakao는 아무것도 안넘겨줘도 됩니다 * google 회원탈퇴시 구글측 accessToken을 param으로 넘겨줘야함 + * apple 회원탈퇴시 애플측 accessToken을 param으로 넘겨줘야함 */ @Operation(summary = "withdraw") @PostMapping("/withdraw") suspend fun tokenRefresh( authUser: AuthUser, @RequestParam code: String?, - @RequestParam accessToken: String?, - ) = authFacade.withdraw(authUser, code, accessToken).wrapVoid() + @RequestParam googleAccessToken: String?, + @RequestParam appleAccessToken: String?, + ) = authFacade.withdraw(authUser, code, googleAccessToken, appleAccessToken).wrapVoid() } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/OAuthResource.kt b/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/OAuthResource.kt index 3648fd7a..3e8922da 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/OAuthResource.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/OAuthResource.kt @@ -21,7 +21,10 @@ class OAuthResource( private val oAuthFacade: OAuthFacade, ) { - /** 가입된 유저인지 체크합니다. */ + /** + * 가입된 유저인지 체크합니다. /n + * APPLE 로그인은 accessToken에 idToken 넣어주세요 + */ @Operation(summary = "register valid check") @GetMapping("/{provider}/sign-up/valid") suspend fun checkRegisterValid( @@ -29,7 +32,10 @@ class OAuthResource( @RequestParam accessToken: String, ) = oAuthFacade.checkRegisterValid(provider, accessToken).wrapOk() - /** 회원가입을 합니다. */ + /** + * 회원가입을 합니다. + * APPLE 로그인은 accessToken에 idToken 넣어주세요 + */ @Operation(summary = "register") @PostMapping("/{provider}/sign-up") suspend fun register( @@ -39,7 +45,10 @@ class OAuthResource( @RequestParam accessToken: String, ) = oAuthFacade.register(provider, accessToken, request, deviceContext).wrapCreated() - /** 로그인을 합니다. */ + /** + * 로그인을 합니다. + * APPLE 로그인은 accessToken에 idToken 넣어주세요 + */ @Operation(summary = "login") @PostMapping("/{provider}/login") suspend fun login( diff --git a/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/OAuthWithdrawResource.kt b/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/OAuthWithdrawResource.kt index b4612695..3dcb9f33 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/OAuthWithdrawResource.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/auth/presentation/OAuthWithdrawResource.kt @@ -27,9 +27,11 @@ class OAuthWithdrawResource( ): String { val kakaoRedirectUrl = oAuthService.getOAuthWithdrawLoginLink(OAuthProvider.KAKAO, request.uri.toString()) val googleRedirectUrl = oAuthService.getOAuthWithdrawLoginLink(OAuthProvider.GOOGLE, request.uri.toString()) + val appleRedirectUrl = oAuthService.getOAuthWithdrawLoginLink(OAuthProvider.APPLE, request.uri.toString()) model.addAttribute("kakaoRedirectUrl", kakaoRedirectUrl.link) model.addAttribute("googleRedirectUrl", googleRedirectUrl.link) + model.addAttribute("appleRedirectUrl", appleRedirectUrl.link) return "withdrawLogin" } @@ -52,7 +54,7 @@ class OAuthWithdrawResource( /** oauth callback용 url, 로그인 완료되면 /withdraw로 redirect */ @GetMapping("/GOOGLE/callback") - suspend fun getGooGleCallbackPage( + suspend fun getGoogleCallbackPage( model: Model, request: ServerHttpRequest, @RequestParam code: String, @@ -67,15 +69,34 @@ class OAuthWithdrawResource( return RedirectView("/withdraw?xSusuAuthToken=$susuToken&googleAccessToken=$googleAccessToken") } + /** oauth callback용 url, 로그인 완료되면 /withdraw로 redirect */ + @GetMapping("/apple/callback") + suspend fun getAppleCallbackPage( + model: Model, + request: ServerHttpRequest, + @RequestParam code: String, + ): RedirectView { + val appleAccessToken = oAuthService.getOAuthWithdrawToken(OAuthProvider.APPLE, code).accessToken + val susuToken = oAuthFacade.login( + provider = OAuthProvider.APPLE, + request = OAuthLoginRequest(appleAccessToken), + deviceContext = UserDeviceContextImpl.getDefault() + ).accessToken + + return RedirectView("/withdraw?xSusuAuthToken=$susuToken&appleAccessToken=$appleAccessToken") + } + /** 회원 탈퇴 페이지 */ @GetMapping("/withdraw") suspend fun getWithdrawPage( model: Model, @RequestParam xSusuAuthToken: String, @RequestParam googleAccessToken: String?, + @RequestParam appleAccessToken: String?, ): String { model.addAttribute("xSusuAuthToken", xSusuAuthToken) model.addAttribute("googleAccessToken", googleAccessToken) + model.addAttribute("appleAccessToken", appleAccessToken) return "withdraw" } } diff --git a/api/src/main/kotlin/com/oksusu/susu/api/config/OAuthSecretConfig.kt b/api/src/main/kotlin/com/oksusu/susu/api/config/OAuthSecretConfig.kt index 356b5154..65c1eb0c 100644 --- a/api/src/main/kotlin/com/oksusu/susu/api/config/OAuthSecretConfig.kt +++ b/api/src/main/kotlin/com/oksusu/susu/api/config/OAuthSecretConfig.kt @@ -35,7 +35,6 @@ class OAuthSecretConfig( @ConfigurationProperties(prefix = "oauth.apple") class AppleOAuthSecretConfig( val clientId: String, - val webClientId: String, val keyId: String, val teamId: String, val authKey: String, diff --git a/api/src/main/resources/config/application.yml b/api/src/main/resources/config/application.yml index dcfb2134..e6a8e34a 100644 --- a/api/src/main/resources/config/application.yml +++ b/api/src/main/resources/config/application.yml @@ -52,14 +52,10 @@ oauth: admin-key: ${KAKAO_ADMIN_KEY} apple: - baseUrl: ${APPLE_BASE_URL} - clientId: ${APPLE_CLIENT_ID} - webClientId: ${APPLE_WEB_CLIENT_ID} - keyId: ${APPLE_KEY_ID} - redirectUrl: ${APPLE_REDIRECT} - teamId: ${APPLE_TEAM_ID} - webCallbackUrl: ${APPLE_WEB_CALLBACK} - authKey: ${APPLE_AUTH_KEY} + client-id: ${APPLE_CLIENT_ID} + key-id: ${APPLE_KEY_ID} + team-id: ${APPLE_TEAM_ID} + auth-key: ${APPLE_AUTH_KEY} google: client-id: ${GOOGLE_CLIENT_ID} diff --git a/api/src/main/resources/static/appleLogin.png b/api/src/main/resources/static/appleLogin.png new file mode 100644 index 00000000..a106a128 Binary files /dev/null and b/api/src/main/resources/static/appleLogin.png differ diff --git a/api/src/main/resources/templates/withdraw.html b/api/src/main/resources/templates/withdraw.html index 9533c09a..f8f904c8 100644 --- a/api/src/main/resources/templates/withdraw.html +++ b/api/src/main/resources/templates/withdraw.html @@ -7,17 +7,17 @@ crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" /> - + - + - + SUSU 회원 탈퇴 @@ -48,15 +49,15 @@ 탈퇴 시 작성했던 데이터를 복구할 수 없어요

-

탈퇴

-
+ th:attr="xSusuAuthToken=${xSusuAuthToken}, googleAccessToken=${googleAccessToken}, appleAccessToken=${appleAccessToken}" + th:onclick="withdrawUser(this.getAttribute('xSusuAuthToken'), this.getAttribute('googleAccessToken'), this.getAttribute('appleAccessToken')))"/> +

탈퇴

- + + diff --git a/api/src/main/resources/templates/withdrawLogin.html b/api/src/main/resources/templates/withdrawLogin.html index 533b23c4..d82c25aa 100644 --- a/api/src/main/resources/templates/withdrawLogin.html +++ b/api/src/main/resources/templates/withdrawLogin.html @@ -34,6 +34,11 @@ +
+ + + +