Skip to content

Commit

Permalink
fix: apple oauth 로직 수정 (#65)
Browse files Browse the repository at this point in the history
* fix: apple oauth secret key 수정 및 회원 탈퇴 페이지 로직 수정

* feat: client failure logging

* fix: apple client secret 생성 로직 변경

* feat: apple client content type 지정

* fix: oidc decode payload 수정

* fix: apple oauth token response 변경

* chore: logging 추가

* chore: 에러 해결용 검증 로직 추가

* chore: 에러 해결용 에러 로깅 추가

* fix: 에러 처리 로직 수정

* fix: 함수 파라미터 변경

* fix: withdraw 로직 수정

* fix: withdraw client Unit 대응하도록 변경

* fix: 주석 수정, deploy branch 수정

* chore: ktlint formatting
  • Loading branch information
wjdtkdgns authored Jul 3, 2024
1 parent 52ee6d3 commit b8d4cbc
Show file tree
Hide file tree
Showing 20 changed files with 122 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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) }
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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!!)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)
Expand All @@ -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())
}

Expand All @@ -83,8 +83,6 @@ class AppleOAuthService(
appleClient.getToken(redirectUrl, code, clientId, clientSecret)
}

getOIDCDecodePayload(tokens.idToken)

return OAuthTokenResponse.fromApple(tokens)
}

Expand All @@ -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
*/
Expand All @@ -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()
)
}
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ data class OidcDecodePayload(
val aud: String,
/** oauth provider account unique id */
val sub: String,
val email: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,21 @@ class OAuthResource(
private val oAuthFacade: OAuthFacade,
) {

/** 가입된 유저인지 체크합니다. */
/**
* 가입된 유저인지 체크합니다. /n
* APPLE 로그인은 accessToken에 idToken 넣어주세요
*/
@Operation(summary = "register valid check")
@GetMapping("/{provider}/sign-up/valid")
suspend fun checkRegisterValid(
@PathVariable provider: OAuthProvider,
@RequestParam accessToken: String,
) = oAuthFacade.checkRegisterValid(provider, accessToken).wrapOk()

/** 회원가입을 합니다. */
/**
* 회원가입을 합니다.
* APPLE 로그인은 accessToken에 idToken 넣어주세요
*/
@Operation(summary = "register")
@PostMapping("/{provider}/sign-up")
suspend fun register(
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand All @@ -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,
Expand All @@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 4 additions & 8 deletions api/src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Binary file added api/src/main/resources/static/appleLogin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 13 additions & 12 deletions api/src/main/resources/templates/withdraw.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/[email protected]/dist/web/static/pretendard.min.css"
/>
<link rel="stylesheet" th:href="@{/css/withdraw.css}" />
<link rel="stylesheet" th:href="@{/css/withdraw.css}"/>
<link rel="apple-touch-icon" sizes="180x180" th:href="@{/apple-touch-icon.png}">
<link rel="icon" type="image/png" sizes="192x192" th:href="@{/susufavicon_16_16.png}">
<link rel="icon" type="image/png" sizes="192x192" th:href="@{/susufavicon_16_16.png}">
<link rel="icon" type="image/png" sizes="32x32" th:href="@{/susufavicon_32_32.png}">
<link rel="icon" type="image/png" sizes="16x16" th:href="@{/susufavicon_16_16.png}">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script th:inline="javascript">
/*<![CDATA[*/
function withdrawUser(xSusuAuthToken, googleAccessToken) {
function withdrawUser(xSusuAuthToken, googleAccessToken, appleAccessToken) {
$.ajax({
url: `/api/v1/auth/withdraw?accessToken=` + googleAccessToken,
url: `/api/v1/auth/withdraw?googleAccessToken=` + googleAccessToken + `appleAccessToken=` + appleAccessToken,
type: 'post',
contentType: 'application/json; charset=utf-8',
dataType: 'application/json',
Expand All @@ -33,9 +33,10 @@
}
});
}

/*]]>*/
</script>
<meta charset="utf-8" />
<meta charset="utf-8"/>
<title>SUSU 회원 탈퇴</title>
</head>
<body>
Expand All @@ -48,15 +49,15 @@
탈퇴 시 작성했던 데이터를 복구할 수 없어요
</p>
<div class="withdrawButton"
th:attr="xSusuAuthToken=${xSusuAuthToken}, googleAccessToken=${googleAccessToken}"
th:onclick="withdrawUser(this.getAttribute('xSusuAuthToken'), this.getAttribute('googleAccessToken'))" />
<p>탈퇴</p>
</div>
th:attr="xSusuAuthToken=${xSusuAuthToken}, googleAccessToken=${googleAccessToken}, appleAccessToken=${appleAccessToken}"
th:onclick="withdrawUser(this.getAttribute('xSusuAuthToken'), this.getAttribute('googleAccessToken'), this.getAttribute('appleAccessToken')))"/>
<p>탈퇴</p>
</div>
</div>
<div class="Logo">
<svg th:replace="~{component/logo}">logo</svg>
</div>
</div>
<div class="Logo">
<svg th:replace="~{component/logo}">logo</svg>
</div>
</div>
</body>
</html>
5 changes: 5 additions & 0 deletions api/src/main/resources/templates/withdrawLogin.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
<img class="Button" th:src="@{/kakaoLogin.png}">
</a>
</div>
<div class="oauthButton" style="margin: 30px 0 0 0">
<a th:href="${appleRedirectUrl}">
<img class="Button" th:src="@{/appleLogin.png}">
</a>
</div>
</div>
</div>
<div class="Logo">
Expand Down
Loading

0 comments on commit b8d4cbc

Please sign in to comment.