Skip to content

Commit

Permalink
[feat #36] 토큰 재발급 API (#70)
Browse files Browse the repository at this point in the history
* feat : Reissue api 필터 미적용

* feat : Reissue dto

* feat : Reissue API

* feat : Reissue 로직 구현

* feat : RefreshToken 쿼리 추가

* feat : user 검증 로직 추가

* feat : 링크 유효성 검증
  • Loading branch information
dlswns2480 authored Aug 5, 2024
1 parent a59f8ad commit aa2fb8c
Show file tree
Hide file tree
Showing 15 changed files with 81 additions and 10 deletions.
15 changes: 15 additions & 0 deletions adapters/in-web/src/main/kotlin/com/pokit/auth/AuthController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package com.pokit.auth

import com.pokit.auth.config.ErrorOperation
import com.pokit.auth.dto.request.ApiRevokeRequest
import com.pokit.auth.dto.request.ReIssueRequest
import com.pokit.auth.dto.request.toDto
import com.pokit.auth.dto.response.ReIssueResponse
import com.pokit.auth.dto.response.toReIssueResponse
import com.pokit.auth.model.PrincipalUser
import com.pokit.auth.model.toDomain
import com.pokit.auth.port.`in`.AuthUseCase
import com.pokit.common.wrapper.ResponseWrapper.wrapOk
import com.pokit.common.wrapper.ResponseWrapper.wrapUnit
import com.pokit.token.dto.request.SignInRequest
import com.pokit.user.exception.UserErrorCode
Expand Down Expand Up @@ -35,4 +39,15 @@ class AuthController(
return authUseCase.withDraw(user.toDomain(), request.toDto())
.wrapUnit()
}

@PostMapping("/reissue")
@Operation(summary = "액세스 토큰 재발급 API")
@ErrorOperation(UserErrorCode::class)
fun reissue(
@RequestBody request: ReIssueRequest
): ResponseEntity<ReIssueResponse> {
return authUseCase.reissue(request.refreshToken)
.toReIssueResponse()
.wrapOk()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ class SecurityConfig(
companion object {
private val WHITE_LIST = arrayOf(
"/api/v1/auth/signin",
"/api/v1/auth/reissue",
"/api/v1/user/interests",
"/swagger-ui/index.html#/",
"/swagger",
"/swagger-ui.html",
"/swagger-ui/**",
"/api-docs",
"/api-docs/**",
"/v3/api-docs/**",
"/v3/api-docs/**"
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.pokit.auth.dto.request

data class ReIssueRequest(
val refreshToken: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.pokit.auth.dto.response

data class ReIssueResponse(
val accessToken: String
)

internal fun String.toReIssueResponse() = ReIssueResponse(
accessToken = this
)
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class CustomAuthenticationFilter(
override fun shouldNotFilter(request: HttpServletRequest): Boolean {
val excludePath = arrayOf(
"/api/v1/auth/signin",
"/api/v1/auth/reissue",
"/api/v1/user/interests",
"/swagger-ui/index.html#/",
"/swagger", "/swagger-ui.html",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.pokit.content.dto.request

import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Pattern
import jakarta.validation.constraints.Size

data class CreateContentRequest(
@field:NotBlank(message = "링크는 필수값입니다.")
@field:Pattern(
regexp = "^(https?|ftp)://[^\\s/\$.?#].[^\\s]*\$",
message = "링크 형식이 올바르지 않습니다."
)
val data: String,
@field:NotBlank(message = "제목은 필수값입니다.")
@field:Size(min = 1, max = 20, message = "최대 20자까지만 입력 가능합니다.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ class RefreshTokenAdapter(
override fun deleteById(id: Long) {
refreshTokenRepository.deleteById(id)
}

override fun deleteByUserId(userId: Long) {
refreshTokenRepository.deleteByUserId(userId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository

interface RefreshTokenRepository : JpaRepository<RefreshTokenEntity, Long> {
fun findByUserId(userId: Long): RefreshTokenEntity?

fun deleteByUserId(userId: Long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ interface AuthUseCase {
fun signIn(request: SignInRequest): Token

fun withDraw(user: User, request: RevokeRequest)

fun reissue(refreshToken: String): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.pokit.token.model.Token
interface TokenProvider {
fun createToken(userId: Long): Token

fun reissueToken(refreshToken: String): String
fun reissueToken(userId: Long, refreshToken: String): String

fun deleteRefreshToken(refreshTokenId: Long)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ interface RefreshTokenPort {
fun loadByUserId(userId: Long): RefreshToken?

fun deleteById(id: Long)

fun deleteByUserId(userId: Long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.pokit.token.exception.AuthErrorCode
import com.pokit.token.model.AuthPlatform
import com.pokit.token.model.Token
import com.pokit.user.dto.UserInfo
import com.pokit.user.exception.UserErrorCode
import com.pokit.user.model.Role
import com.pokit.user.model.User
import com.pokit.user.port.out.UserPort
Expand All @@ -25,7 +26,7 @@ class AuthService(
private val appleApiClient: AppleApiClient,
private val tokenProvider: TokenProvider,
private val userPort: UserPort,
private val contentPort: ContentPort
private val contentPort: ContentPort,
) : AuthUseCase {
@Transactional
override fun signIn(request: SignInRequest): Token {
Expand Down Expand Up @@ -58,6 +59,13 @@ class AuthService(
userPort.delete(user)
}

override fun reissue(refreshToken: String): String {
val userId = tokenProvider.getUserId(refreshToken)
userPort.loadById(userId)
?: throw ClientValidationException(UserErrorCode.NOT_FOUND_USER)
return tokenProvider.reissueToken(userId, refreshToken)
}

private fun createUser(userInfo: UserInfo): User {
val user = User(email = userInfo.email, role = Role.USER, authPlatform = userInfo.authPlatform)
return userPort.persist(user)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,21 @@ class JwtTokenProvider(
override fun createToken(userId: Long): Token {
val accessToken = generateToken(userId, jwtProperty.accessExpiryTime)
val refreshToken = generateToken(userId, jwtProperty.refreshExpiryTime)

refreshTokenPort.deleteByUserId(userId)
refreshTokenPort.persist(RefreshToken(userId, refreshToken))

return Token(accessToken, refreshToken)
}

override fun reissueToken(refreshToken: String): String {
val userId = getUserId(refreshToken)
refreshTokenPort.loadByUserId(userId)
override fun reissueToken(userId: Long, refreshToken: String): String {
val findRefreshToken = refreshTokenPort.loadByUserId(userId)
?: throw ClientValidationException(AuthErrorCode.NOT_FOUND_TOKEN)

if (findRefreshToken.token != refreshToken) {
throw ClientValidationException(AuthErrorCode.INVALID_TOKEN)
}

return generateToken(userId, jwtProperty.accessExpiryTime)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.pokit.auth.port.`in`.TokenProvider
import com.pokit.auth.port.out.AppleApiClient
import com.pokit.auth.port.out.GoogleApiClient
import com.pokit.common.exception.ClientValidationException
import com.pokit.content.port.out.ContentPort
import com.pokit.user.UserFixture
import com.pokit.user.port.out.UserPort
import io.kotest.assertions.throwables.shouldThrow
Expand All @@ -20,20 +21,23 @@ class AuthServiceTest : BehaviorSpec({
val tokenProvider = mockk<TokenProvider>()
val userPort = mockk<UserPort>()
val appleApiClient = mockk<AppleApiClient>()
val authService = AuthService(googleApiClient, appleApiClient, tokenProvider, userPort)
val contentPort = mockk<ContentPort>()
val authService = AuthService(googleApiClient, appleApiClient, tokenProvider, userPort, contentPort)

Given("사용자가 로그인할 때") {
Given("사용자가 인증 관련하여 요청 시") {
val request = AuthFixture.getGoogleSigniInRequest()
val invalidPlatformRequst = AuthFixture.getInvalidSignInRequest() // 플랫폼명 오타 요청
val user = UserFixture.getUser()
val userInfo = UserFixture.getUserInfo()
val token = AuthFixture.getToken()
val reissueResult = "accessToken"

every { googleApiClient.getUserInfo(request.idToken) } returns userInfo
every { userPort.loadByEmail(userInfo.email) } returns user
every { tokenProvider.createToken(user.id) } returns token
every { tokenProvider.reissueToken("refresh") } returns reissueResult

When("구글 플랫폼으로 올바른 인증코드로 요청하면") {
When("로그인할 때 구글 플랫폼으로 올바른 인증코드로 요청하면") {
val resultToken = authService.signIn(request)
Then("토큰이 정상적으로 반환된다.") {
resultToken.accessToken shouldBe token.accessToken
Expand All @@ -47,5 +51,11 @@ class AuthServiceTest : BehaviorSpec({
}
}
}
When("Refresh Token으로 토큰 재발급을 요청하면") {
val result = authService.reissue("refresh")
Then("액세스 토큰을 재발급한다.") {
result shouldBe reissueResult
}
}
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.pokit.user.port.service

import com.pokit.common.exception.ClientValidationException
import com.pokit.common.exception.NotFoundCustomException
import com.pokit.token.model.AuthPlatform
import com.pokit.user.UserFixture
import com.pokit.user.dto.request.UpdateNicknameRequest
import com.pokit.user.model.User
Expand All @@ -19,7 +20,7 @@ class UserServiceTest : BehaviorSpec({
val user = UserFixture.getUser()
val invalidUser = UserFixture.getInvalidUser()
val request = UserFixture.getSignUpRequest()
val modifieUser = User(user.id, user.email, user.role, request.nickName)
val modifieUser = User(user.id, user.email, user.role, request.nickName, AuthPlatform.GOOGLE)

every { userPort.register(user) } returns modifieUser
every { userPort.register(invalidUser) } returns null
Expand Down

0 comments on commit aa2fb8c

Please sign in to comment.