Skip to content

Commit

Permalink
[Bug] 로그아웃, 사용자 탈퇴 시 HTTP Cookie Header에 존재하는 RefreshToken를 만료시키도록 적용…
Browse files Browse the repository at this point in the history
…한다 (#164)

* feat: RefreshToken Cookie Header 만료 로직

* fix: 로그아웃, 사용자 탈퇴 시 RefreshToken Cookie Header 만료 로직 적용

* refactor: OAuthApi Kotlin 전환

* test: 로그아웃, 사용자 탈퇴 AcceptanceTest에 대해서 RefreshToken 만료 검증 로직 추가

* refactor: TokenReissueApi Kotlin 전환

* refactor: ManageAccountApi Kotlin 전환

* refactor: Kotlin Data Class Request validator 동작하도록 `@field` 적용

* remove SignUpResponse
  • Loading branch information
sjiwon authored Feb 20, 2024
1 parent 2b3e828 commit ce0da0f
Show file tree
Hide file tree
Showing 22 changed files with 360 additions and 386 deletions.
81 changes: 0 additions & 81 deletions src/main/java/com/koddy/server/auth/presentation/OAuthApi.java

This file was deleted.

80 changes: 80 additions & 0 deletions src/main/java/com/koddy/server/auth/presentation/OAuthApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.koddy.server.auth.presentation

import com.koddy.server.auth.application.usecase.GetOAuthLinkUseCase
import com.koddy.server.auth.application.usecase.LogoutUseCase
import com.koddy.server.auth.application.usecase.OAuthLoginUseCase
import com.koddy.server.auth.application.usecase.command.LogoutCommand
import com.koddy.server.auth.application.usecase.query.GetOAuthLink
import com.koddy.server.auth.domain.model.AuthMember
import com.koddy.server.auth.domain.model.Authenticated
import com.koddy.server.auth.domain.model.oauth.OAuthProvider
import com.koddy.server.auth.presentation.request.OAuthLoginRequest
import com.koddy.server.auth.presentation.response.LoginResponse
import com.koddy.server.auth.utils.TokenResponseWriter
import com.koddy.server.global.ResponseWrapper
import com.koddy.server.global.annotation.Auth
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.servlet.http.HttpServletResponse
import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@Tag(name = "1-1. OAuth 인증 관련 API")
@RestController
@RequestMapping("/api/oauth")
class OAuthApi(
private val getOAuthLinkUseCase: GetOAuthLinkUseCase,
private val oAuthLoginUseCase: OAuthLoginUseCase,
private val logoutUseCase: LogoutUseCase,
private val tokenResponseWriter: TokenResponseWriter,
) {
@Operation(summary = "Provider별 OAuth 인증을 위한 URL을 받는 EndPoint")
@GetMapping(value = ["/access/{provider}"], params = ["redirectUri"])
fun queryOAuthLink(
@PathVariable provider: String,
@RequestParam redirectUri: String,
): ResponseEntity<ResponseWrapper<String>> {
val oAuthLink: String = getOAuthLinkUseCase.invoke(
GetOAuthLink(
OAuthProvider.from(provider),
redirectUri,
),
)
return ResponseEntity.ok(ResponseWrapper(oAuthLink))
}

@Operation(summary = "Authorization Code를 통해서 Provider별 인증을 위한 EndPoint")
@PostMapping("/login/{provider}")
fun login(
@PathVariable provider: String,
@RequestBody @Valid request: OAuthLoginRequest,
response: HttpServletResponse,
): ResponseEntity<LoginResponse> {
val authMember: AuthMember = oAuthLoginUseCase.invoke(request.toCommand(provider))
tokenResponseWriter.applyToken(response, authMember.token)
return ResponseEntity.ok(
LoginResponse(
id = authMember.id,
name = authMember.name,
),
)
}

@Operation(summary = "로그아웃 EndPoint")
@PostMapping("/logout")
fun logout(
@Auth authenticated: Authenticated,
response: HttpServletResponse,
): ResponseEntity<Void> {
logoutUseCase.invoke(LogoutCommand(authenticated.id))
tokenResponseWriter.expireRefreshTokenCookie(response)
return ResponseEntity.noContent().build()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.koddy.server.auth.presentation

import com.koddy.server.auth.application.usecase.ReissueTokenUseCase
import com.koddy.server.auth.application.usecase.command.ReissueTokenCommand
import com.koddy.server.auth.domain.model.AuthToken
import com.koddy.server.auth.domain.model.TokenType
import com.koddy.server.auth.utils.TokenResponseWriter
import com.koddy.server.global.annotation.ExtractToken
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@Tag(name = "1-2. 토큰 재발급 API")
@RestController
@RequestMapping("/api/token/reissue")
class TokenReissueApi(
private val reissueTokenUseCase: ReissueTokenUseCase,
private val tokenResponseWriter: TokenResponseWriter,
) {
@Operation(summary = "RefreshToken을 통한 토큰 재발급 Endpoint")
@PostMapping
fun reissueToken(
@ExtractToken(tokenType = TokenType.REFRESH) refreshToken: String,
response: HttpServletResponse,
): ResponseEntity<Void> {
val authToken: AuthToken = reissueTokenUseCase.invoke(ReissueTokenCommand(refreshToken))
tokenResponseWriter.applyToken(response, authToken)
return ResponseEntity.noContent().build()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.koddy.server.auth.presentation.request

import com.koddy.server.auth.application.usecase.command.OAuthLoginCommand
import com.koddy.server.auth.domain.model.oauth.OAuthProvider
import jakarta.validation.constraints.NotBlank

data class OAuthLoginRequest(
@field:NotBlank(message = "Authorization Code는 필수입니다.")
val authorizationCode: String,

@field:NotBlank(message = "Redirect Uri는 필수입니다.")
val redirectUri: String,

@field:NotBlank(message = "State값은 필수입니다.")
val state: String,
) {
fun toCommand(provider: String): OAuthLoginCommand =
OAuthLoginCommand(
OAuthProvider.from(provider),
authorizationCode,
redirectUri,
state,
)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.koddy.server.auth.presentation.response

data class LoginResponse(
val id: Long,
val name: String,
)
11 changes: 11 additions & 0 deletions src/main/java/com/koddy/server/auth/utils/TokenResponseWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ public void applyToken(final HttpServletResponse response, final AuthToken token
applyRefreshToken(response, token.refreshToken());
}

public void expireRefreshTokenCookie(final HttpServletResponse response) {
final ResponseCookie cookie = ResponseCookie.from(REFRESH_TOKEN_HEADER, "")
.maxAge(1)
.sameSite(Cookie.SameSite.NONE.attributeValue())
.secure(true)
.httpOnly(true)
.path("/")
.build();
response.setHeader(SET_COOKIE, cookie.toString());
}

private void applyAccessToken(final HttpServletResponse response, final String accessToken) {
response.setHeader(ACCESS_TOKEN_HEADER, String.join(" ", TOKEN_TYPE, accessToken));
}
Expand Down

This file was deleted.

Loading

0 comments on commit ce0da0f

Please sign in to comment.