From 9787c7b00b42f181cd7f333bcf98fca5eaea6a3c Mon Sep 17 00:00:00 2001 From: gurdl7011 Date: Fri, 1 Dec 2023 16:09:24 +0900 Subject: [PATCH] Apple OAuth --- maeumgagym-core/build.gradle.kts | 2 + .../auth/dto/response}/ApplePublicKey.kt | 2 +- .../auth/dto/response}/ApplePublicKeys.kt | 2 +- .../auth/port/in/AppleLoginUseCase.kt | 8 +++ .../auth/port/out/AppleJwtParsePort.kt | 6 ++ .../auth/port/out/GenerateJwtPort.kt | 1 + .../auth/port/out/GeneratePublicKeyPort.kt | 12 ++++ .../auth/port/out/JwtExpiredCheckPort.kt | 6 -- .../auth/port/out/ParsePublicKeyPort.kt | 9 +++ .../auth/port/out/ReadApplePublicKeyPort.kt | 8 +++ .../auth/service/AppleLoginService.kt | 46 ++++++++++++ .../auth/adapter/AppleAuthAdapter.kt | 13 ++++ .../auth/adapter/GeneratePublicKeyAdapter.kt | 36 ++++++++++ .../feign/oauth/apple/AppleClient.kt | 2 +- .../maeumgagym/global/jwt/AppleJwtParser.kt | 31 ++++++++ .../info/maeumgagym/global/jwt/JwtAdapter.kt | 70 +++++++++++++++++++ .../info/maeumgagym/global/jwt/JwtFilter.kt | 29 ++++++++ .../{security/token => jwt}/JwtResolver.kt | 2 +- .../global/security/FilterConfig.kt | 6 +- .../global/security/SecurityConfig.kt | 4 +- .../global/security/jwt/JwtFilter.kt | 37 ---------- .../global/security/token/JwtAdapter.kt | 53 -------------- .../auth/controller/AppleController.kt | 16 +++++ 23 files changed, 296 insertions(+), 105 deletions(-) rename {maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/dto => maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/dto/response}/ApplePublicKey.kt (91%) rename {maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/dto => maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/dto/response}/ApplePublicKeys.kt (79%) create mode 100644 maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/in/AppleLoginUseCase.kt create mode 100644 maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/AppleJwtParsePort.kt create mode 100644 maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/GeneratePublicKeyPort.kt delete mode 100644 maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/JwtExpiredCheckPort.kt create mode 100644 maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/ParsePublicKeyPort.kt create mode 100644 maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/ReadApplePublicKeyPort.kt create mode 100644 maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/AppleLoginService.kt create mode 100644 maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/auth/adapter/AppleAuthAdapter.kt create mode 100644 maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/auth/adapter/GeneratePublicKeyAdapter.kt create mode 100644 maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/AppleJwtParser.kt create mode 100644 maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtAdapter.kt create mode 100644 maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtFilter.kt rename maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/{security/token => jwt}/JwtResolver.kt (90%) delete mode 100644 maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/jwt/JwtFilter.kt delete mode 100644 maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/token/JwtAdapter.kt create mode 100644 maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/auth/controller/AppleController.kt diff --git a/maeumgagym-core/build.gradle.kts b/maeumgagym-core/build.gradle.kts index ea5f5ba38..307d4dc79 100644 --- a/maeumgagym-core/build.gradle.kts +++ b/maeumgagym-core/build.gradle.kts @@ -7,10 +7,12 @@ dependencies { implementation(project(":maeumgagym-common")) implementation(Dependencies.SPRING_TRANSACTION) + implementation(Dependencies.JWT) } allOpen { annotation("com.info.common.UseCase") + annotation("org.springframework.stereotype.Service") annotation("com.info.common.PersistenceAdapter") annotation("com.info.common.WebAdapter") } diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/dto/ApplePublicKey.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/dto/response/ApplePublicKey.kt similarity index 91% rename from maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/dto/ApplePublicKey.kt rename to maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/dto/response/ApplePublicKey.kt index 5754c2451..11f7cad1e 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/dto/ApplePublicKey.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/dto/response/ApplePublicKey.kt @@ -1,4 +1,4 @@ -package com.info.maeumgagym.feign.oauth.apple.dto +package com.info.maeumgagym.auth.dto.response import org.springframework.util.Base64Utils import java.math.BigInteger diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/dto/ApplePublicKeys.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/dto/response/ApplePublicKeys.kt similarity index 79% rename from maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/dto/ApplePublicKeys.kt rename to maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/dto/response/ApplePublicKeys.kt index 5eb1eb905..a59cf9b94 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/dto/ApplePublicKeys.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/dto/response/ApplePublicKeys.kt @@ -1,4 +1,4 @@ -package com.info.maeumgagym.feign.oauth.apple.dto +package com.info.maeumgagym.auth.dto.response data class ApplePublicKeys( val keys: MutableList diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/in/AppleLoginUseCase.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/in/AppleLoginUseCase.kt new file mode 100644 index 000000000..26910d677 --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/in/AppleLoginUseCase.kt @@ -0,0 +1,8 @@ +package com.info.maeumgagym.auth.port.`in` + +import com.info.maeumgagym.auth.dto.response.TokenResponse + +interface AppleLoginUseCase { + + fun execute(token: String): TokenResponse +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/AppleJwtParsePort.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/AppleJwtParsePort.kt new file mode 100644 index 000000000..6ea9dc945 --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/AppleJwtParsePort.kt @@ -0,0 +1,6 @@ +package com.info.maeumgagym.auth.port.out + +interface AppleJwtParsePort { + + fun parseHeaders(token: String): MutableMap +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/GenerateJwtPort.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/GenerateJwtPort.kt index cf8ab627d..52fdabc55 100644 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/GenerateJwtPort.kt +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/GenerateJwtPort.kt @@ -4,5 +4,6 @@ import com.info.maeumgagym.auth.dto.response.TokenResponse import java.util.UUID interface GenerateJwtPort { + fun generateToken(userId: UUID): TokenResponse } diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/GeneratePublicKeyPort.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/GeneratePublicKeyPort.kt new file mode 100644 index 000000000..d8a611502 --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/GeneratePublicKeyPort.kt @@ -0,0 +1,12 @@ +package com.info.maeumgagym.auth.port.out + +import com.info.maeumgagym.auth.dto.response.ApplePublicKeys +import java.security.PublicKey + +interface GeneratePublicKeyPort { + + fun generatePublicKey( + tokenHeaders: MutableMap, + applePublicKeys: ApplePublicKeys + ): PublicKey +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/JwtExpiredCheckPort.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/JwtExpiredCheckPort.kt deleted file mode 100644 index 4f02ab02f..000000000 --- a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/JwtExpiredCheckPort.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.info.maeumgagym.auth.port.out - -interface JwtExpiredCheckPort { - - fun getSubjectWithExpiredCheck(token: String): String -} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/ParsePublicKeyPort.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/ParsePublicKeyPort.kt new file mode 100644 index 000000000..2a6575be0 --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/ParsePublicKeyPort.kt @@ -0,0 +1,9 @@ +package com.info.maeumgagym.auth.port.out + +import io.jsonwebtoken.Claims +import java.security.PublicKey + +interface ParsePublicKeyPort { + + fun parseClaimsFromPublicKey(token: String, publicKey: PublicKey): Claims +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/ReadApplePublicKeyPort.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/ReadApplePublicKeyPort.kt new file mode 100644 index 000000000..2cfc100d6 --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/port/out/ReadApplePublicKeyPort.kt @@ -0,0 +1,8 @@ +package com.info.maeumgagym.auth.port.out + +import com.info.maeumgagym.auth.dto.response.ApplePublicKeys + +interface ReadApplePublicKeyPort { + + fun readPublicKey(): ApplePublicKeys +} diff --git a/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/AppleLoginService.kt b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/AppleLoginService.kt new file mode 100644 index 000000000..37fb8e4a1 --- /dev/null +++ b/maeumgagym-core/src/main/kotlin/com/info/maeumgagym/auth/service/AppleLoginService.kt @@ -0,0 +1,46 @@ +package com.info.maeumgagym.auth.service + +import com.info.maeumgagym.auth.dto.response.TokenResponse +import com.info.maeumgagym.auth.port.`in`.AppleLoginUseCase +import com.info.maeumgagym.auth.port.out.* +import com.info.maeumgagym.user.model.Role +import com.info.maeumgagym.user.model.User +import com.info.maeumgagym.user.port.out.CreateUserPort +import com.info.maeumgagym.user.port.out.FindUserByOAuthIdPort +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class AppleLoginService( + private val appleJwtParsePort: AppleJwtParsePort, + private val generatePublicKeyPort: GeneratePublicKeyPort, + private val parsePublicKeyPort: ParsePublicKeyPort, + private val readApplePublicKey: ReadApplePublicKeyPort, + private val findUserByOAuthIdPort: FindUserByOAuthIdPort, + private val createUserPort: CreateUserPort, + private val generateJwtPort: GenerateJwtPort +): AppleLoginUseCase{ + + @Transactional + override fun execute(token: String): TokenResponse { + + val idToken = parseIdToken(token) + + val sub = idToken.subject + + val user: User = findUserByOAuthIdPort.findUserByOAuthId(sub) ?: createUserPort.createUser( + User( + nickname = idToken.get("name", String::class.java), + roles = mutableListOf(Role.USER), + oauthId = sub, + profilePath = null + ) + ) + + return generateJwtPort.generateToken(user.id) + } + + private fun parseIdToken(token: String) = parsePublicKeyPort.parseClaimsFromPublicKey( + token, generatePublicKeyPort.generatePublicKey(appleJwtParsePort.parseHeaders(token), readApplePublicKey.readPublicKey()) + ) +} diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/auth/adapter/AppleAuthAdapter.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/auth/adapter/AppleAuthAdapter.kt new file mode 100644 index 000000000..ac045313e --- /dev/null +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/auth/adapter/AppleAuthAdapter.kt @@ -0,0 +1,13 @@ +package com.info.maeumgagym.auth.adapter + +import com.info.common.WebAdapter +import com.info.maeumgagym.auth.port.out.ReadApplePublicKeyPort +import com.info.maeumgagym.feign.oauth.apple.AppleClient + +@WebAdapter +class AppleAuthAdapter( + private val appleClient: AppleClient +): ReadApplePublicKeyPort { + + override fun readPublicKey() = appleClient.applePublicKeys() +} diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/auth/adapter/GeneratePublicKeyAdapter.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/auth/adapter/GeneratePublicKeyAdapter.kt new file mode 100644 index 000000000..e477ce69a --- /dev/null +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/auth/adapter/GeneratePublicKeyAdapter.kt @@ -0,0 +1,36 @@ +package com.info.maeumgagym.auth.adapter + +import com.info.common.WebAdapter +import com.info.maeumgagym.auth.dto.response.ApplePublicKey +import com.info.maeumgagym.auth.dto.response.ApplePublicKeys +import com.info.maeumgagym.auth.port.out.GeneratePublicKeyPort +import com.info.maeumgagym.global.exception.InvalidTokenException +import java.security.KeyFactory +import java.security.NoSuchAlgorithmException +import java.security.PublicKey +import java.security.spec.InvalidKeySpecException + +@WebAdapter +class GeneratePublicKeyAdapter: GeneratePublicKeyPort { + + private companion object { + const val ALG_HEADER_KEY = "alg" + const val KID_HEADER_KEY = "kid" + } + + override fun generatePublicKey(tokenHeaders: MutableMap, applePublicKeys: ApplePublicKeys): PublicKey { + + val publicKey: ApplePublicKey = applePublicKeys.matchesKey( + tokenHeaders[ALG_HEADER_KEY]!!, + tokenHeaders[KID_HEADER_KEY]!! + ) ?: throw InvalidTokenException + + return try { + KeyFactory.getInstance(publicKey.kty).generatePublic(publicKey.publicKeySpec()) + } catch (e: NoSuchAlgorithmException) { + throw IllegalStateException("Apple OAuth 로그인 중 public key 생성에 문제가 발생했습니다.") + } catch (e: InvalidKeySpecException) { + throw IllegalStateException("Apple OAuth 로그인 중 public key 생성에 문제가 발생했습니다.") + } + } +} diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/AppleClient.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/AppleClient.kt index f5e2b3b16..6ef4c6f70 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/AppleClient.kt +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/feign/oauth/apple/AppleClient.kt @@ -1,7 +1,7 @@ package com.info.maeumgagym.feign.oauth.apple +import com.info.maeumgagym.auth.dto.response.ApplePublicKeys import com.info.maeumgagym.global.config.feign.FeignConfig -import com.info.maeumgagym.feign.oauth.apple.dto.ApplePublicKeys import org.springframework.cloud.openfeign.FeignClient import org.springframework.web.bind.annotation.GetMapping diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/AppleJwtParser.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/AppleJwtParser.kt new file mode 100644 index 000000000..3601d677e --- /dev/null +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/AppleJwtParser.kt @@ -0,0 +1,31 @@ +package com.info.maeumgagym.global.jwt + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.ObjectMapper +import com.info.maeumgagym.auth.port.out.AppleJwtParsePort +import com.info.maeumgagym.global.exception.InvalidTokenException +import org.springframework.stereotype.Component +import org.springframework.util.Base64Utils + + +@Component +class AppleJwtParser(private val objectMapper: ObjectMapper): AppleJwtParsePort { + + companion object { + private const val IDENTITY_TOKEN_VALUE_DELIMITER = "\\." + private const val HEADER_INDEX = 0 + } + + @Suppress("unchecked_cast") + override fun parseHeaders(token: String): MutableMap { + return try { + val encodedHeader: String = token.split(IDENTITY_TOKEN_VALUE_DELIMITER.toRegex())[HEADER_INDEX] + val decodedHeader = String(Base64Utils.decodeFromUrlSafeString(encodedHeader)) + objectMapper.readValue(decodedHeader, MutableMap::class.java) as MutableMap + } catch (e: JsonProcessingException) { + throw InvalidTokenException + } catch (e: ArrayIndexOutOfBoundsException) { + throw InvalidTokenException + } + } +} diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtAdapter.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtAdapter.kt new file mode 100644 index 000000000..d2c15a34d --- /dev/null +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtAdapter.kt @@ -0,0 +1,70 @@ +package com.info.maeumgagym.global.jwt + +import com.info.maeumgagym.auth.dto.response.TokenResponse +import com.info.maeumgagym.auth.port.out.GenerateJwtPort +import com.info.maeumgagym.auth.port.out.ParsePublicKeyPort +import com.info.maeumgagym.global.env.jwt.JwtProperties +import com.info.maeumgagym.global.exception.ExpiredTokenException +import com.info.maeumgagym.global.exception.InvalidTokenException +import com.info.maeumgagym.global.security.principle.CustomUserDetailService +import com.info.maeumgagym.global.security.principle.CustomUserDetails +import io.jsonwebtoken.* +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication +import org.springframework.stereotype.Component +import java.security.PublicKey +import java.util.* + +@Component +class JwtAdapter( + val jwtProperties: JwtProperties, + private val customUserDetailService: CustomUserDetailService +) : GenerateJwtPort, ParsePublicKeyPort { + + override fun generateToken(userId: UUID): TokenResponse { + return TokenResponse( + generateAccessToken(userId) + ) + } + + private fun generateAccessToken(userId: UUID): String { + val now = Date() + return Jwts.builder() + .setSubject(userId.toString()) + .setIssuedAt(now) + .setExpiration(Date(now.time + jwtProperties.accessExpiredExp * 1000L)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secretKey) + .compact() + } + + fun getAuthentication(token: String): Authentication { + + val subject = getBody(token).subject + + val authDetails = customUserDetailService.loadUserByUsername(subject) as CustomUserDetails + + return UsernamePasswordAuthenticationToken(authDetails, null, authDetails.authorities) + } + + private fun getBody(token: String): Claims { + try { + return Jwts.parser().setSigningKey(jwtProperties.secretKey).parseClaimsJws(token).body + } catch (e: JwtException) { + when (e) { + is ExpiredJwtException -> throw ExpiredTokenException + else -> throw InvalidTokenException + } + } + } + + override fun parseClaimsFromPublicKey(token: String, publicKey: PublicKey): Claims { + return try { + Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).body + } catch (e: Exception) { + when (e) { + is ExpiredJwtException -> throw ExpiredTokenException + else -> throw InvalidTokenException + } + } + } +} diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtFilter.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtFilter.kt new file mode 100644 index 000000000..f8924add8 --- /dev/null +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtFilter.kt @@ -0,0 +1,29 @@ +package com.info.maeumgagym.global.jwt + +import com.info.maeumgagym.global.security.principle.CustomUserDetailService +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.web.filter.OncePerRequestFilter +import javax.servlet.FilterChain +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +class JwtFilter( + customUserDetailService: CustomUserDetailService, + private val jwtResolver: JwtResolver, + private val jwtAdapter: JwtAdapter +) : OncePerRequestFilter() { + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + + jwtResolver.resolveToken(request) + ?.let { + SecurityContextHolder.getContext().authentication = jwtAdapter.getAuthentication(it) + + filterChain.doFilter(request, response) + } + } +} diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/token/JwtResolver.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtResolver.kt similarity index 90% rename from maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/token/JwtResolver.kt rename to maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtResolver.kt index c8e34d1ad..ce34471fb 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/token/JwtResolver.kt +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/jwt/JwtResolver.kt @@ -1,4 +1,4 @@ -package com.info.maeumgagym.global.security.token +package com.info.maeumgagym.global.jwt import com.info.maeumgagym.global.env.jwt.JwtProperties import org.springframework.stereotype.Component diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/FilterConfig.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/FilterConfig.kt index bf94c0555..33d9c8244 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/FilterConfig.kt +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/FilterConfig.kt @@ -2,10 +2,10 @@ package com.info.maeumgagym.global.security import com.fasterxml.jackson.databind.ObjectMapper import com.info.maeumgagym.global.error.GlobalExceptionFilter -import com.info.maeumgagym.global.security.jwt.JwtFilter +import com.info.maeumgagym.global.jwt.JwtFilter import com.info.maeumgagym.global.security.principle.CustomUserDetailService -import com.info.maeumgagym.global.security.token.JwtAdapter -import com.info.maeumgagym.global.security.token.JwtResolver +import com.info.maeumgagym.global.jwt.JwtAdapter +import com.info.maeumgagym.global.jwt.JwtResolver import org.springframework.security.config.annotation.SecurityConfigurerAdapter import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.web.DefaultSecurityFilterChain diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/SecurityConfig.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/SecurityConfig.kt index fd263de02..c1874a452 100644 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/SecurityConfig.kt +++ b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/SecurityConfig.kt @@ -2,8 +2,8 @@ package com.info.maeumgagym.global.security import com.fasterxml.jackson.databind.ObjectMapper import com.info.maeumgagym.global.security.principle.CustomUserDetailService -import com.info.maeumgagym.global.security.token.JwtAdapter -import com.info.maeumgagym.global.security.token.JwtResolver +import com.info.maeumgagym.global.jwt.JwtAdapter +import com.info.maeumgagym.global.jwt.JwtResolver import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/jwt/JwtFilter.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/jwt/JwtFilter.kt deleted file mode 100644 index 5b46b5ca6..000000000 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/jwt/JwtFilter.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.info.maeumgagym.global.security.jwt - -import com.info.maeumgagym.global.security.principle.CustomUserDetailService -import com.info.maeumgagym.global.security.token.JwtResolver -import com.info.maeumgagym.global.security.token.JwtAdapter -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.core.Authentication -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.core.userdetails.UserDetails -import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -class JwtFilter( - private val customUserDetailService: CustomUserDetailService, - private val jwtResolver: JwtResolver, - private val jwtAdapter: JwtAdapter -) : OncePerRequestFilter() { - - override fun doFilterInternal( - request: HttpServletRequest, - response: HttpServletResponse, - filterChain: FilterChain - ) { - val resolveToken: String? = jwtResolver.resolveToken(request) - - if (resolveToken != null) { - val accountId = jwtAdapter.getSubjectWithExpiredCheck(resolveToken) - val userDetails: UserDetails = customUserDetailService.loadUserByUsername(accountId) - val authentication: Authentication = - UsernamePasswordAuthenticationToken(userDetails, "", userDetails.authorities) - SecurityContextHolder.getContext().authentication = authentication - } - filterChain.doFilter(request, response) - } -} diff --git a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/token/JwtAdapter.kt b/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/token/JwtAdapter.kt deleted file mode 100644 index d8b323103..000000000 --- a/maeumgagym-infrastructure/src/main/kotlin/com/info/maeumgagym/global/security/token/JwtAdapter.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.info.maeumgagym.global.security.token - -import com.info.maeumgagym.auth.dto.response.TokenResponse -import com.info.maeumgagym.auth.port.out.GenerateJwtPort -import com.info.maeumgagym.auth.port.out.JwtExpiredCheckPort -import com.info.maeumgagym.global.env.jwt.JwtProperties -import com.info.maeumgagym.global.exception.ExpiredTokenException -import com.info.maeumgagym.global.exception.InvalidTokenException -import io.jsonwebtoken.Claims -import io.jsonwebtoken.JwtException -import io.jsonwebtoken.Jwts -import io.jsonwebtoken.SignatureAlgorithm -import org.springframework.stereotype.Component -import java.util.* - -@Component -class JwtAdapter( - val jwtProperties: JwtProperties -) : GenerateJwtPort, JwtExpiredCheckPort { - override fun generateToken(userId: UUID): TokenResponse { - return TokenResponse( - generateAccessToken(userId) - ) - } - - private fun generateAccessToken(userId: UUID): String { - val now = Date() - return Jwts.builder() - .setSubject(userId.toString()) - .setIssuedAt(now) - .setExpiration(Date(now.time + jwtProperties.accessExpiredExp * 1000L)) - .signWith(SignatureAlgorithm.HS256, jwtProperties.secretKey) - .compact() - } - - override fun getSubjectWithExpiredCheck(token: String): String { - val body = getBody(token) - - if (body.expiration.before(Date())) { - throw ExpiredTokenException - } else { - return body.subject - } - } - - private fun getBody(token: String): Claims { - try { - return Jwts.parser().setSigningKey(jwtProperties.secretKey).parseClaimsJws(token).body - } catch (e: JwtException) { - throw InvalidTokenException - } - } -} diff --git a/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/auth/controller/AppleController.kt b/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/auth/controller/AppleController.kt new file mode 100644 index 000000000..63e968e9b --- /dev/null +++ b/maeumgagym-presentation/src/main/kotlin/com/info/maeumgagym/auth/controller/AppleController.kt @@ -0,0 +1,16 @@ +package com.info.maeumgagym.auth.controller + +import com.info.maeumgagym.auth.dto.response.TokenResponse +import com.info.maeumgagym.auth.port.`in`.AppleLoginUseCase +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/apple") +class AppleController(private val appleLoginUseCase: AppleLoginUseCase) { + + @PostMapping("/login") + fun login(@RequestParam("token", required = true) token: String): TokenResponse = appleLoginUseCase.execute(token) +}