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:
pwy3282040@msecure.co
2025-12-26 12:58:51 +09:00
parent 02909894db
commit 49d435079f
15 changed files with 549 additions and 6 deletions

View File

@@ -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)