feat: JWT 기반 인증 시스템 및 이메일 가입 구현
[인프라] - Docker Compose 구성 (DB, Redis, MinIO) - Spring Boot 3.5.9 + Kotlin + Gradle 설정 [인증/보안] - Spring Security 및 JWT 필터 설정 - RTR(Refresh Token Rotation) 방식의 토큰 재발급 로직 구현 - Redis를 활용한 Refresh Token 및 이메일 인증 코드 관리 [기능 구현] - 회원가입 (이메일 인증 포함) - 로그인/로그아웃/토큰재발급 API 구현 - 공통 응답(ApiResponse) 및 전역 예외 처리(GlobalExceptionHandler) 적용
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package me.wypark.blogbackend.api.common
|
||||
|
||||
data class ApiResponse<T>(
|
||||
val code: String = "SUCCESS",
|
||||
val message: String = "요청이 성공했습니다.",
|
||||
val data: T? = null
|
||||
) {
|
||||
companion object {
|
||||
fun <T> success(data: T? = null, message: String = "요청이 성공했습니다."): ApiResponse<T> {
|
||||
return ApiResponse("SUCCESS", message, data)
|
||||
}
|
||||
|
||||
fun error(message: String, code: String = "ERROR"): ApiResponse<Nothing> {
|
||||
return ApiResponse(code, message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.wypark.blogbackend.api.controller
|
||||
|
||||
import jakarta.validation.Valid
|
||||
import me.wypark.blogbackend.api.common.ApiResponse
|
||||
import me.wypark.blogbackend.api.dto.*
|
||||
import me.wypark.blogbackend.domain.auth.AuthService
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
class AuthController(
|
||||
private val authService: AuthService
|
||||
) {
|
||||
|
||||
@PostMapping("/signup")
|
||||
fun signup(@RequestBody @Valid request: SignupRequest): ResponseEntity<ApiResponse<Nothing>> {
|
||||
authService.signup(request)
|
||||
return ResponseEntity.ok(ApiResponse.success(message = "회원가입에 성공했습니다. 이메일 인증을 완료해주세요."))
|
||||
}
|
||||
|
||||
@PostMapping("/verify")
|
||||
fun verifyEmail(@RequestBody @Valid request: VerifyEmailRequest): ResponseEntity<ApiResponse<Nothing>> {
|
||||
authService.verifyEmail(request.email, request.code)
|
||||
return ResponseEntity.ok(ApiResponse.success(message = "이메일 인증이 완료되었습니다."))
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
fun login(@RequestBody @Valid request: LoginRequest): ResponseEntity<ApiResponse<TokenDto>> {
|
||||
val tokenDto = authService.login(request)
|
||||
return ResponseEntity.ok(ApiResponse.success(tokenDto))
|
||||
}
|
||||
|
||||
@PostMapping("/reissue")
|
||||
fun reissue(@RequestBody request: ReissueRequest): ResponseEntity<ApiResponse<TokenDto>> {
|
||||
val tokenDto = authService.reissue(request.accessToken, request.refreshToken)
|
||||
return ResponseEntity.ok(ApiResponse.success(tokenDto))
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
fun logout(@AuthenticationPrincipal user: User): ResponseEntity<ApiResponse<Nothing>> {
|
||||
authService.logout(user.username) // user.username은 email입니다.
|
||||
return ResponseEntity.ok(ApiResponse.success(message = "로그아웃 되었습니다."))
|
||||
}
|
||||
}
|
||||
|
||||
data class ReissueRequest(val accessToken: String, val refreshToken: String)
|
||||
37
src/main/kotlin/me/wypark/blogbackend/api/dto/AuthDtos.kt
Normal file
37
src/main/kotlin/me/wypark/blogbackend/api/dto/AuthDtos.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package me.wypark.blogbackend.api.dto
|
||||
|
||||
import jakarta.validation.constraints.Email
|
||||
import jakarta.validation.constraints.NotBlank
|
||||
import jakarta.validation.constraints.Size
|
||||
|
||||
// 회원가입 요청
|
||||
data class SignupRequest(
|
||||
@field:NotBlank(message = "이메일은 필수입니다.")
|
||||
@field:Email(message = "올바른 이메일 형식이 아닙니다.")
|
||||
val email: String,
|
||||
|
||||
@field:NotBlank(message = "비밀번호는 필수입니다.")
|
||||
@field:Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하로 입력해주세요.")
|
||||
val password: String,
|
||||
|
||||
@field:NotBlank(message = "닉네임은 필수입니다.")
|
||||
@field:Size(min = 2, max = 10, message = "닉네임은 2자 이상 10자 이하로 입력해주세요.")
|
||||
val nickname: String
|
||||
)
|
||||
|
||||
// 로그인 요청
|
||||
data class LoginRequest(
|
||||
@field:NotBlank(message = "이메일을 입력해주세요.")
|
||||
val email: String,
|
||||
|
||||
@field:NotBlank(message = "비밀번호를 입력해주세요.")
|
||||
val password: String
|
||||
)
|
||||
|
||||
data class VerifyEmailRequest(
|
||||
@field:NotBlank(message = "이메일을 입력해주세요")
|
||||
val email: String,
|
||||
|
||||
@field:NotBlank(message = "인증 코드를 입력해주세요")
|
||||
val code: String
|
||||
)
|
||||
Reference in New Issue
Block a user