diff --git a/src/main/java/com/be/sportizebe/domain/user/controller/UserController.java b/src/main/java/com/be/sportizebe/domain/user/controller/UserController.java index 16cb060..2eb49b5 100644 --- a/src/main/java/com/be/sportizebe/domain/user/controller/UserController.java +++ b/src/main/java/com/be/sportizebe/domain/user/controller/UserController.java @@ -1,5 +1,6 @@ package com.be.sportizebe.domain.user.controller; +import com.be.sportizebe.domain.user.dto.request.ChangePasswordRequest; import com.be.sportizebe.domain.user.dto.request.SignUpRequest; import com.be.sportizebe.domain.user.dto.request.UpdateProfileRequest; import com.be.sportizebe.domain.user.dto.response.ProfileImageResponse; @@ -64,4 +65,14 @@ public ResponseEntity> getMyInfo( UserInfoResponse response = userService.getUserInfo(userAuthInfo.getId()); return ResponseEntity.ok(BaseResponse.success("사용자 정보 조회 성공", response)); } + + @PutMapping("/password") + @Operation(summary = "비밀번호 변경", description = "현재 비밀번호를 확인 후 새 비밀번호로 변경합니다.") + public ResponseEntity> changePassword( + @AuthenticationPrincipal UserAuthInfo userAuthInfo, + @RequestBody @Valid ChangePasswordRequest request + ) { + userService.changePassword(userAuthInfo.getId(), request); + return ResponseEntity.ok(BaseResponse.success("비밀번호 변경 성공", null)); + } } \ No newline at end of file diff --git a/src/main/java/com/be/sportizebe/domain/user/dto/request/ChangePasswordRequest.java b/src/main/java/com/be/sportizebe/domain/user/dto/request/ChangePasswordRequest.java new file mode 100644 index 0000000..5ec817d --- /dev/null +++ b/src/main/java/com/be/sportizebe/domain/user/dto/request/ChangePasswordRequest.java @@ -0,0 +1,18 @@ +package com.be.sportizebe.domain.user.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +@Schema(description = "비밀번호 변경 요청") +public record ChangePasswordRequest( + @Schema(description = "현재 비밀번호") + @NotBlank(message = "현재 비밀번호는 필수입니다.") + String currentPassword, + + @Schema(description = "새 비밀번호") + @NotBlank(message = "새 비밀번호는 필수입니다.") + @Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하로 입력해주세요.") + String newPassword +) { +} \ No newline at end of file diff --git a/src/main/java/com/be/sportizebe/domain/user/entity/User.java b/src/main/java/com/be/sportizebe/domain/user/entity/User.java index 9568d6e..8461eec 100644 --- a/src/main/java/com/be/sportizebe/domain/user/entity/User.java +++ b/src/main/java/com/be/sportizebe/domain/user/entity/User.java @@ -80,4 +80,8 @@ public void updateProfile(String nickname, String introduce) { this.nickname = nickname; this.introduce = introduce; } + + public void updatePassword(String encodedPassword) { + this.password = encodedPassword; + } } diff --git a/src/main/java/com/be/sportizebe/domain/user/exception/UserErrorCode.java b/src/main/java/com/be/sportizebe/domain/user/exception/UserErrorCode.java index a526707..2006c28 100644 --- a/src/main/java/com/be/sportizebe/domain/user/exception/UserErrorCode.java +++ b/src/main/java/com/be/sportizebe/domain/user/exception/UserErrorCode.java @@ -18,7 +18,9 @@ public enum UserErrorCode implements BaseErrorCode { USER_NOT_FOUND("USER_0005", "회원을 찾을 수 없습니다.", HttpStatus.NOT_FOUND), USER_DELETE_FAILED("USER_0006", "사용자 삭제에 실패했습니다.", HttpStatus.INTERNAL_SERVER_ERROR), - S3_DELETE_FAILED("USER_0007", "S3 이미지 삭제에 실패했습니다.", HttpStatus.INTERNAL_SERVER_ERROR); + S3_DELETE_FAILED("USER_0007", "S3 이미지 삭제에 실패했습니다.", HttpStatus.INTERNAL_SERVER_ERROR), + CURRENT_PASSWORD_MISMATCH("USER_0009", "현재 비밀번호가 일치하지 않습니다.", HttpStatus.BAD_REQUEST), + SAME_PASSWORD("USER_0010", "새 비밀번호는 현재 비밀번호와 달라야 합니다.", HttpStatus.BAD_REQUEST); private final String code; private final String message; diff --git a/src/main/java/com/be/sportizebe/domain/user/service/UserService.java b/src/main/java/com/be/sportizebe/domain/user/service/UserService.java index d5fbf79..8d2267d 100644 --- a/src/main/java/com/be/sportizebe/domain/user/service/UserService.java +++ b/src/main/java/com/be/sportizebe/domain/user/service/UserService.java @@ -1,5 +1,6 @@ package com.be.sportizebe.domain.user.service; +import com.be.sportizebe.domain.user.dto.request.ChangePasswordRequest; import com.be.sportizebe.domain.user.dto.request.SignUpRequest; import com.be.sportizebe.domain.user.dto.request.UpdateProfileRequest; import com.be.sportizebe.domain.user.dto.response.ProfileImageResponse; @@ -21,4 +22,7 @@ public interface UserService { // 사용자 정보 조회 UserInfoResponse getUserInfo(Long userId); + + // 비밀번호 변경 + void changePassword(Long userId, ChangePasswordRequest request); } diff --git a/src/main/java/com/be/sportizebe/domain/user/service/UserServiceImpl.java b/src/main/java/com/be/sportizebe/domain/user/service/UserServiceImpl.java index 7053208..ed07490 100644 --- a/src/main/java/com/be/sportizebe/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/be/sportizebe/domain/user/service/UserServiceImpl.java @@ -1,5 +1,6 @@ package com.be.sportizebe.domain.user.service; +import com.be.sportizebe.domain.user.dto.request.ChangePasswordRequest; import com.be.sportizebe.domain.user.dto.request.SignUpRequest; import com.be.sportizebe.domain.user.dto.request.UpdateProfileRequest; import com.be.sportizebe.domain.user.dto.response.ProfileImageResponse; @@ -27,7 +28,7 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; + private final PasswordEncoder passwordEncoder; // 비밀번호 인코딩 목적 private final UserCacheService userCacheService; private final S3Service s3Service; @@ -119,4 +120,27 @@ public UserInfoResponse getUserInfo(Long userId) { return UserInfoResponse.from(userAuthInfo); } + + @Override + @Transactional + public void changePassword(Long userId, ChangePasswordRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND)); + + // 현재 비밀번호 확인 + if (!passwordEncoder.matches(request.currentPassword(), user.getPassword())) { + throw new CustomException(UserErrorCode.CURRENT_PASSWORD_MISMATCH); + } + + // 새 비밀번호가 현재 비밀번호와 동일한지 확인 + if (passwordEncoder.matches(request.newPassword(), user.getPassword())) { + throw new CustomException(UserErrorCode.SAME_PASSWORD); + } + + // 비밀번호 변경 + String encodedNewPassword = passwordEncoder.encode(request.newPassword()); + user.updatePassword(encodedNewPassword); + + log.info("사용자 비밀번호 변경 완료: userId={}", userId); + } } \ No newline at end of file