Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pida-clients/oauth-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ dependencies {
implementation(libs.bouncycastle.bcpkix)
implementation(libs.bundles.openfeign)

implementation(libs.jjwt.api)
runtimeOnly(libs.jjwt.jackson)
runtimeOnly(libs.jjwt.impl)

implementation(project(":pida-core:core-domain"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ package com.pida.client.oauth
import com.pida.client.oauth.response.ApplePublicKeysResponse
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam

@FeignClient(name = "apple-auth-api", url = "https://appleid.apple.com")
internal interface AppleApi {
@GetMapping("/auth/keys")
fun getApplePublicKeys(): ApplePublicKeysResponse

@PostMapping("/auth/revoke", consumes = ["application/x-www-form-urlencoded"])
fun revokeToken(
@RequestParam("client_id") clientId: String,
@RequestParam("client_secret") clientSecret: String,
@RequestParam("token") token: String,
@RequestParam("token_type_hint") tokenTypeHint: String = "refresh_token",
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@ import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import com.pida.support.error.AuthenticationErrorException
import com.pida.support.error.AuthenticationErrorType
import io.jsonwebtoken.Jwts
import org.springframework.stereotype.Component
import java.nio.file.Files
import java.nio.file.Paths
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.text.ParseException
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.Base64
import java.util.Date

@Component
Expand Down Expand Up @@ -40,6 +49,16 @@ class AppleClient internal constructor(
}
}

fun revoke(token: String) {
val clientSecret = generateAppleClientSecret()

appleApi.revokeToken(
appleProperties.bundleId,
clientSecret,
token,
)
}

fun verify(token: String): Boolean {
val signedJWT: SignedJWT
val jwtClaims: JWTClaimsSet
Expand All @@ -54,7 +73,6 @@ class AppleClient internal constructor(
return false
}
val currentDate = Date(System.currentTimeMillis())
// audience 확인 λΆ€λΆ„ κ°œμ„ 
val bundleId = jwtClaims.audience.firstOrNull()
if (bundleId != appleProperties.bundleId) return false

Expand Down Expand Up @@ -82,4 +100,55 @@ class AppleClient internal constructor(
}
return false
}

fun generateAppleClientSecret(): String {
val expirationDate =
Date.from(
LocalDateTime
.now()
.plusMinutes(5)
.atZone(ZoneId.systemDefault())
.toInstant(),
)

val teamId = appleProperties.teamId
val clientId = appleProperties.bundleId
val keyId = appleProperties.keyId

val now = Date()

val jwtBuilder =
Jwts
.builder()
.header()
.add("kid", keyId)
.add("alg", "ES256")
.and()
.issuer(teamId)
.issuedAt(now)
.expiration(expirationDate)
.audience()
.add("https://appleid.apple.com")
.and()
.subject(clientId)

return jwtBuilder
.signWith(getPrivateKey())
.compact()
}

fun getPrivateKey(): PrivateKey {
val p8 = appleProperties.privateKey
val keyContent =
Files
.readAllLines(Paths.get(p8))
.filterNot { it.startsWith("-----") }
.joinToString("")

val decoded = Base64.getDecoder().decode(keyContent)
val keySpec = PKCS8EncodedKeySpec(decoded)
val keyFactory = KeyFactory.getInstance("EC")

return keyFactory.generatePrivate(keySpec)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("apple")
data class AppleProperties(
val bundleId: String,
val keyId: String,
val teamId: String,
val privateKey: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ class OAuthService(
if (!appleClient.verify(token)) throw AuthenticationErrorException(AuthenticationErrorType.INVALID_APPLE_TOKEN)
return appleClient.getUserInfo(token)
}

fun revokeApple(refreshToken: String) {
appleClient.revoke(refreshToken)
}
}
3 changes: 3 additions & 0 deletions pida-clients/oauth-client/src/main/resources/oauth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ spring:

apple:
bundle-id: ${APPLE_BUNDLE_ID}
key-id: ${APPLE_KEY_ID}
team-id: ${APPLE_TEAM_ID}
private-key: ${APPLE_P8}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.pida.presentation.v1.user

import com.pida.auth.AuthenticationService
import com.pida.client.oauth.OAuthService
import com.pida.presentation.v1.annotation.ApiV1Controller
import com.pida.presentation.v1.user.request.AppleRefreshTokenRequest
import com.pida.presentation.v1.user.request.UpdateNicknameRequest
import com.pida.presentation.v1.user.response.UserProfileResponse
import com.pida.presentation.v1.user.response.UserWithdrawalResponse
Expand All @@ -21,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestBody
class UserController(
private val userService: UserService,
private val authenticationService: AuthenticationService,
private val oAuthService: OAuthService,
) {
@Operation(summary = "λ‚΄ 정보 쑰회", description = "λ‚΄ 정보λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€.")
@GetMapping("/users/me")
Expand All @@ -35,13 +38,16 @@ class UserController(
@DeleteMapping("/users")
suspend fun withdrawal(
@Parameter(hidden = true, required = false) user: User,
@RequestBody request: AppleRefreshTokenRequest,
): UserWithdrawalResponse {
userService.deleteUser(
NewUserWithdrawal(
user = user,
),
)
authenticationService.delete(user.key)

oAuthService.revokeApple(request.refreshToken)
return UserWithdrawalResponse("νšŒμ›νƒˆν‡΄κ°€ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.pida.presentation.v1.user.request

import io.swagger.v3.oas.annotations.media.Schema

@Schema(description = "μ• ν”Œ 토큰 만료 μš”μ²­")
data class AppleRefreshTokenRequest(
val refreshToken: String,
)
Loading