fix(member-social): 애플 로그인 aud 검증에 serviceId를 포함한다

This commit is contained in:
2026-03-30 09:21:59 +09:00
parent 2160e7b9dd
commit a4ffab0351
4 changed files with 109 additions and 3 deletions

View File

@@ -20,7 +20,9 @@ import java.util.Date
@Service
class AppleIdentityTokenVerifier(
@Value("\${apple.bundle-id}")
private val bundleId: String
private val bundleId: String,
@Value("\${apple.service-id}")
private val serviceId: String
) {
private val jwkUrl = URL("https://appleid.apple.com/auth/keys")
private val jwkSource: JWKSource<SecurityContext> = JWKSourceBuilder.create<SecurityContext>(jwkUrl)
@@ -32,7 +34,8 @@ class AppleIdentityTokenVerifier(
}
fun verify(identityToken: String, rawNonce: String): AppleUserInfo {
if (bundleId.isBlank()) {
val expectedAudiences = resolveExpectedAudiences()
if (expectedAudiences.isEmpty()) {
throw SodaException(messageKey = "member.social.apple_login_failed")
}
@@ -59,7 +62,7 @@ class AppleIdentityTokenVerifier(
throw SodaException(messageKey = "member.social.apple_login_failed")
}
if (!claims.audience.contains(bundleId)) {
if (!isSupportedAudience(claims.audience)) {
throw SodaException(messageKey = "member.social.apple_login_failed")
}
@@ -81,6 +84,18 @@ class AppleIdentityTokenVerifier(
}
}
internal fun isSupportedAudience(audience: List<String>): Boolean {
val expectedAudiences = resolveExpectedAudiences()
return expectedAudiences.isNotEmpty() && audience.any { expectedAudiences.contains(it) }
}
private fun resolveExpectedAudiences(): Set<String> {
return setOf(bundleId, serviceId)
.map { it.trim() }
.filter { it.isNotBlank() }
.toSet()
}
private fun hashNonce(rawNonce: String): String {
val digest = MessageDigest.getInstance("SHA-256")
val hashed = digest.digest(rawNonce.toByteArray(StandardCharsets.UTF_8))