diff --git a/src/main/kotlin/com/back/koreaTravelGuide/common/exception/GlobalExceptionHandler.kt b/src/main/kotlin/com/back/koreaTravelGuide/common/exception/GlobalExceptionHandler.kt index 40610c1..8d0f674 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/common/exception/GlobalExceptionHandler.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/common/exception/GlobalExceptionHandler.kt @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler * @Valid 검증 실패 → 400 * throw IllegalArgumentException("메시지") → 400 * throw NoSuchElementException("메시지") → 404 + * throw IllegalStateException("메시지") → 409 * 기타 모든 예외 → 500 */ @ControllerAdvice @@ -41,6 +42,12 @@ class GlobalExceptionHandler { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse(ex.message ?: "데이터를 찾을 수 없습니다")) } + @ExceptionHandler(IllegalStateException::class) + fun handleIllegalState(ex: IllegalStateException): ResponseEntity> { + logger.warn("부적절한 상태: {}", ex.message) + return ResponseEntity.status(HttpStatus.CONFLICT).body(ApiResponse(ex.message ?: "요청을 처리할 수 없는 상태입니다")) + } + @ExceptionHandler(Exception::class) fun handleGenericException( ex: Exception, diff --git a/src/main/kotlin/com/back/koreaTravelGuide/common/security/AuthenticationExtensions.kt b/src/main/kotlin/com/back/koreaTravelGuide/common/security/AuthenticationExtensions.kt new file mode 100644 index 0000000..e708be6 --- /dev/null +++ b/src/main/kotlin/com/back/koreaTravelGuide/common/security/AuthenticationExtensions.kt @@ -0,0 +1,10 @@ +package com.back.koreaTravelGuide.common.security + +import org.springframework.security.core.Authentication + +fun Authentication.getUserId(): Long { + if (principal is Long) { + return principal as Long + } + throw IllegalStateException("인증된 사용자 ID를 찾을 수 없거나 타입이 올바르지 않습니다.") +} diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/auth/controller/AuthController.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/auth/controller/AuthController.kt index b750e04..7312ee4 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/auth/controller/AuthController.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/auth/controller/AuthController.kt @@ -1,6 +1,7 @@ package com.back.koreaTravelGuide.domain.auth.controller import com.back.koreaTravelGuide.common.ApiResponse +import com.back.koreaTravelGuide.common.security.getUserId import com.back.koreaTravelGuide.domain.auth.dto.request.UserRoleUpdateRequest import com.back.koreaTravelGuide.domain.auth.dto.response.AccessTokenResponse import com.back.koreaTravelGuide.domain.auth.dto.response.LoginResponse @@ -10,7 +11,7 @@ import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.springframework.beans.factory.annotation.Value import org.springframework.http.ResponseEntity -import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.core.Authentication import org.springframework.web.bind.annotation.CookieValue import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -45,9 +46,10 @@ class AuthController( @Operation(summary = "신규 사용자 역할 선택") @PostMapping("/role") fun updateUserRole( - @AuthenticationPrincipal userId: Long, + authentication: Authentication, @RequestBody request: UserRoleUpdateRequest, ): ResponseEntity> { + val userId = authentication.getUserId() val loginResponse = authService.updateRoleAndLogin(userId, request.role) return ResponseEntity.ok(ApiResponse("역할이 선택되었으며 로그인에 성공했습니다.", loginResponse)) } diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/rate/controller/RateController.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/rate/controller/RateController.kt index ca617d4..0324088 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/rate/controller/RateController.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/rate/controller/RateController.kt @@ -1,6 +1,7 @@ package com.back.koreaTravelGuide.domain.rate.controller import com.back.koreaTravelGuide.common.ApiResponse +import com.back.koreaTravelGuide.common.security.getUserId import com.back.koreaTravelGuide.domain.rate.dto.GuideRatingSummaryResponse import com.back.koreaTravelGuide.domain.rate.dto.RateRequest import com.back.koreaTravelGuide.domain.rate.dto.RateResponse @@ -10,7 +11,7 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.core.Authentication import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PutMapping @@ -26,10 +27,11 @@ class RateController( @Operation(summary = "가이드 평가 생성/수정") @PutMapping("/guides/{guideId}") fun rateGuide( - @AuthenticationPrincipal raterUserId: Long, + authentication: Authentication, @PathVariable guideId: Long, @RequestBody request: RateRequest, ): ResponseEntity> { + val raterUserId = authentication.getUserId() val rate = rateService.rateGuide(raterUserId, guideId, request.rating, request.comment) return ResponseEntity.ok(ApiResponse("가이드 평가가 등록되었습니다.", RateResponse.from(rate))) } @@ -37,10 +39,11 @@ class RateController( @Operation(summary = "AI 채팅 세션 평가 생성/수정") @PutMapping("/aichat/sessions/{sessionId}") fun rateAiSession( - @AuthenticationPrincipal raterUserId: Long, + authentication: Authentication, @PathVariable sessionId: Long, @RequestBody request: RateRequest, ): ResponseEntity> { + val raterUserId = authentication.getUserId() val rate = rateService.rateAiSession(raterUserId, sessionId, request.rating, request.comment) return ResponseEntity.ok(ApiResponse("AI 채팅 평가가 등록되었습니다.", RateResponse.from(rate))) } @@ -48,9 +51,8 @@ class RateController( @Operation(summary = "내가 받은 가이드 평가 조회") @GetMapping("/guides/my") @PreAuthorize("hasRole('GUIDE')") - fun getMyGuideRatings( - @AuthenticationPrincipal guideId: Long, - ): ResponseEntity> { + fun getMyGuideRatings(authentication: Authentication): ResponseEntity> { + val guideId = authentication.getUserId() val summary = rateService.getMyGuideRatingSummary(guideId) return ResponseEntity.ok(ApiResponse("내 가이드 평점 정보를 조회했습니다.", summary)) } diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/controller/GuideController.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/controller/GuideController.kt index 5944fb7..b96d522 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/controller/GuideController.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/controller/GuideController.kt @@ -1,6 +1,7 @@ package com.back.koreaTravelGuide.domain.guide.controller import com.back.koreaTravelGuide.common.ApiResponse +import com.back.koreaTravelGuide.common.security.getUserId import com.back.koreaTravelGuide.domain.guide.service.GuideService import com.back.koreaTravelGuide.domain.user.dto.request.GuideUpdateRequest import com.back.koreaTravelGuide.domain.user.dto.response.GuideResponse @@ -8,7 +9,7 @@ import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.core.Authentication import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PatchMapping import org.springframework.web.bind.annotation.PathVariable @@ -42,9 +43,10 @@ class GuideController( @PreAuthorize("hasRole('GUIDE')") @PatchMapping("/me") fun updateMyGuideProfile( - @AuthenticationPrincipal guideId: Long, + authentication: Authentication, @RequestBody request: GuideUpdateRequest, ): ResponseEntity> { + val guideId = authentication.getUserId() val updatedGuideProfile = guideService.updateGuideProfile(guideId, request) return ResponseEntity.ok(ApiResponse("가이드 정보가 성공적으로 수정되었습니다.", updatedGuideProfile)) } diff --git a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/controller/UserController.kt b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/controller/UserController.kt index 32d1fab..8cde527 100644 --- a/src/main/kotlin/com/back/koreaTravelGuide/domain/user/controller/UserController.kt +++ b/src/main/kotlin/com/back/koreaTravelGuide/domain/user/controller/UserController.kt @@ -1,12 +1,13 @@ package com.back.koreaTravelGuide.domain.user.controller import com.back.koreaTravelGuide.common.ApiResponse +import com.back.koreaTravelGuide.common.security.getUserId import com.back.koreaTravelGuide.domain.user.dto.request.UserUpdateRequest import com.back.koreaTravelGuide.domain.user.dto.response.UserResponse import com.back.koreaTravelGuide.domain.user.service.UserService import io.swagger.v3.oas.annotations.Operation import org.springframework.http.ResponseEntity -import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.security.core.Authentication import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PatchMapping @@ -21,9 +22,8 @@ class UserController( ) { @Operation(summary = "내 정보 조회") @GetMapping("/me") - fun getMyProfile( - @AuthenticationPrincipal userId: Long, - ): ResponseEntity> { + fun getMyProfile(authentication: Authentication): ResponseEntity> { + val userId = authentication.getUserId() val userProfile = userService.getUserProfileById(userId) return ResponseEntity.ok(ApiResponse("내 정보를 성공적으로 조회했습니다.", userProfile)) } @@ -31,18 +31,18 @@ class UserController( @Operation(summary = "내 프로필 수정") @PatchMapping("/me") fun updateMyProfile( - @AuthenticationPrincipal userId: Long, + authentication: Authentication, @RequestBody request: UserUpdateRequest, ): ResponseEntity> { + val userId = authentication.getUserId() val updatedProfile = userService.updateUserProfile(userId, request) return ResponseEntity.ok(ApiResponse("정보가 성공적으로 수정되었습니다.", updatedProfile)) } @Operation(summary = "회원 탈퇴") @DeleteMapping("/me") - fun deleteMe( - @AuthenticationPrincipal userId: Long, - ): ResponseEntity> { + fun deleteMe(authentication: Authentication): ResponseEntity> { + val userId = authentication.getUserId() userService.deleteUser(userId) return ResponseEntity.ok(ApiResponse("회원 탈퇴가 완료되었습니다.")) }