Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 6 additions & 0 deletions api/src/main/kotlin/handler/AuthHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ class AuthHandler(
userService.loginKakao(socialLoginRequest)
}

suspend fun loginAppleLegacy(req: ServerRequest): ServerResponse =
handle(req) {
val socialLoginRequest: SocialLoginRequest = req.awaitBodyOrNull() ?: throw ServerWebInputException("Invalid body")
userService.loginApple(socialLoginRequest)
}

suspend fun loginApple(req: ServerRequest): ServerResponse =
handle(req) {
val socialLoginRequest: SocialLoginRequest = req.awaitBodyOrNull() ?: throw ServerWebInputException("Invalid body")
Expand Down
1 change: 1 addition & 0 deletions api/src/main/kotlin/router/MainRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class MainRouter(
POST("/login/facebook", authHandler::loginFacebook)
POST("/login/google", authHandler::loginGoogle)
POST("/login/kakao", authHandler::loginKakao)
POST("/login_apple", authHandler::loginAppleLegacy)
POST("/login/apple", authHandler::loginApple)
POST("/logout", authHandler::logout)
POST("/password/reset/email/check", authHandler::getMaskedEmail)
Expand Down
48 changes: 48 additions & 0 deletions api/src/main/kotlin/router/docs/AuthDocs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,54 @@ import org.springframework.web.bind.annotation.RequestMethod
],
),
),
RouterOperation(
path = "/v1/auth/login_apple",
method = [RequestMethod.POST],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "loginAppleLegacy",
requestBody =
RequestBody(
content = [
Content(
schema = Schema(implementation = SocialLoginRequest::class),
mediaType = MediaType.APPLICATION_JSON_VALUE,
),
],
),
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = LoginResponse::class))],
),
],
),
),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

레거시는 레거시니까 docs에서 빼주세요
괜히 두개 있으면 위에꺼 쓸 수도 있고 더 헷갈려요

RouterOperation(
path = "/v1/auth/login/apple",
method = [RequestMethod.POST],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "loginApple",
requestBody =
RequestBody(
content = [
Content(
schema = Schema(implementation = SocialLoginRequest::class),
mediaType = MediaType.APPLICATION_JSON_VALUE,
),
],
),
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = LoginResponse::class))],
),
],
),
),
RouterOperation(
path = "/v1/auth/logout",
method = [RequestMethod.POST],
Expand Down
19 changes: 15 additions & 4 deletions core/src/main/kotlin/auth/apple/AppleClient.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wafflestudio.snu4t.auth.apple

import com.fasterxml.jackson.databind.ObjectMapper
import com.wafflestudio.snu4t.auth.OAuth2Client
import com.wafflestudio.snu4t.auth.OAuth2UserResponse
import com.wafflestudio.snu4t.common.extension.get
Expand All @@ -18,6 +19,7 @@ import java.util.Base64
@Component("APPLE")
class AppleClient(
webClientBuilder: WebClient.Builder,
private val objectMapper: ObjectMapper,
) : OAuth2Client {
private val webClient =
webClientBuilder.clientConnector(
Expand All @@ -35,9 +37,9 @@ class AppleClient(
override suspend fun getMe(token: String): OAuth2UserResponse? {
val jwtHeader = extractJwtHeader(token)
val appleJwk =
webClient.get<List<AppleJwk>>(uri = APPLE_JWK_URI).getOrNull()
?.find {
it.kid == jwtHeader.keyId && it.alg == jwtHeader.algorithm
webClient.get<Map<String, List<AppleJwk>>>(uri = APPLE_JWK_URI).getOrNull()
?.get("keys")?.find {
it.kid == jwtHeader["kid"] && it.alg == jwtHeader["alg"]
} ?: return null
val publicKey = convertJwkToPublicKey(appleJwk)
val jwtPayload = verifyAndDecodeToken(token, publicKey)
Expand All @@ -51,7 +53,16 @@ class AppleClient(
)
}

private suspend fun extractJwtHeader(token: String) = Jwts.parser().parseClaimsJws(token).header
private suspend fun extractJwtHeader(token: String): Map<String, String> {
val headerJson = Base64.getDecoder().decode(token.substringBefore(".")).toString(Charsets.UTF_8)
val headerMap = objectMapper.readValue(headerJson, Map::class.java)
val kid = headerMap["kid"] as? String ?: throw IllegalArgumentException("유효하지 않은 애플 로그인 토큰")
val alg = headerMap["alg"] as? String ?: throw IllegalArgumentException("유효하지 않은 애플 로그인 토큰")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예상 가능한 상황이니 Snu4tException 만들어서 던지는건 어떨까요

return mapOf(
"kid" to kid,
"alg" to alg,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 AppleClient 내부에 internal data class 만들어서 관리하면 좋을 것 같아요

}

private suspend fun convertJwkToPublicKey(jwk: AppleJwk): PublicKey {
val modulus = BigInteger(1, Base64.getUrlDecoder().decode(jwk.n))
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/kotlin/users/dto/SocialLoginRequest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package com.wafflestudio.snu4t.users.dto
import com.fasterxml.jackson.annotation.JsonAlias

data class SocialLoginRequest(
@JsonAlias("fb_token")
@JsonAlias("fb_token", "apple_token")
val token: String,
)
Loading