diff --git a/refrigerator/.idea/modules/refrigerator.main.iml b/refrigerator/.idea/modules/refrigerator.main.iml index ac8e328..397c268 100644 --- a/refrigerator/.idea/modules/refrigerator.main.iml +++ b/refrigerator/.idea/modules/refrigerator.main.iml @@ -1,6 +1,9 @@ + + + @@ -17,5 +20,4 @@ - - + \ No newline at end of file diff --git a/refrigerator/build.gradle b/refrigerator/build.gradle index bc46bee..156aa6d 100644 --- a/refrigerator/build.gradle +++ b/refrigerator/build.gradle @@ -41,6 +41,8 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' testImplementation 'org.springframework.boot:spring-boot-starter-test' + + implementation 'org.springframework.boot:spring-boot-starter-mail' } tasks.named('test') { useJUnitPlatform() diff --git a/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java b/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java index cbd031e..3dd637c 100644 --- a/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java +++ b/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java @@ -1,9 +1,12 @@ package moja.refrigerator.controller.user; import jakarta.servlet.http.HttpServletRequest; +import moja.refrigerator.dto.user.request.PasswordResetRequest; +import moja.refrigerator.dto.user.request.PasswordUpdateRequest; import moja.refrigerator.dto.user.request.UserCreateRequest; import moja.refrigerator.dto.user.request.UserUpdateRequest; import moja.refrigerator.service.user.UserService; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -25,7 +28,7 @@ public String getMainPage() { @PostMapping("/auth/join") public ResponseEntity join(@RequestBody UserCreateRequest request) { userService.createUser(request); - return ResponseEntity.ok().body("회원 가입이 완료되었습니다."); + return ResponseEntity.status(HttpStatus.CREATED).body("회원 가입이 완료되었습니다."); } // 회원 정보 수정 @@ -34,4 +37,18 @@ public ResponseEntity update(@RequestBody UserUpdateRequest request) { userService.updateUser(request); return ResponseEntity.ok().body("회원 정보가 수정되었습니다."); } + + // 비밀번호 재발급 + @PostMapping("/password/reset") + public ResponseEntity resetPassword(@RequestBody PasswordResetRequest request) { + userService.resetPassword(request); + return ResponseEntity.ok().body("임시 비밀번호가 이메일로 발송되었습니다."); + } + + // 비밀번호 재설정 + @PutMapping("/password/update") + public ResponseEntity updatePassword(@RequestBody PasswordUpdateRequest request) { + userService.updatePassword(request); + return ResponseEntity.ok().body("비밀번호가 변경되었습니다."); + } } diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordResetRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordResetRequest.java new file mode 100644 index 0000000..89b2c75 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordResetRequest.java @@ -0,0 +1,8 @@ +package moja.refrigerator.dto.user.request; + +import lombok.Data; + +@Data +public class PasswordResetRequest { + private String userEmail; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordUpdateRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordUpdateRequest.java new file mode 100644 index 0000000..3b6762a --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordUpdateRequest.java @@ -0,0 +1,9 @@ +package moja.refrigerator.dto.user.request; + +import lombok.Data; + +@Data +public class PasswordUpdateRequest { + private String currentPw; + private String newPw; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/user/UserRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/user/UserRepository.java index b3b44c0..1742c4b 100644 --- a/refrigerator/src/main/java/moja/refrigerator/repository/user/UserRepository.java +++ b/refrigerator/src/main/java/moja/refrigerator/repository/user/UserRepository.java @@ -13,4 +13,5 @@ public interface UserRepository extends JpaRepository { boolean existsByUserNickname(String userNickname); Optional findByUserPk(long userPk); Optional findByUserId(String userId); + Optional findByUserEmail(String userEmail); } diff --git a/refrigerator/src/main/java/moja/refrigerator/service/email/EmailService.java b/refrigerator/src/main/java/moja/refrigerator/service/email/EmailService.java new file mode 100644 index 0000000..c189c53 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/email/EmailService.java @@ -0,0 +1,5 @@ +package moja.refrigerator.service.email; + +public interface EmailService { + void sendTempPassword(String name, String email, String tempPassword); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/email/EmailServiceImpl.java b/refrigerator/src/main/java/moja/refrigerator/service/email/EmailServiceImpl.java new file mode 100644 index 0000000..ec915b3 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/email/EmailServiceImpl.java @@ -0,0 +1,38 @@ +package moja.refrigerator.service.email; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class EmailServiceImpl implements EmailService { + private final JavaMailSender mailSender; + + public EmailServiceImpl(JavaMailSender mailSender) { + this.mailSender = mailSender; + } + + @Value("${spring.mail.username}") + private String senderEmail; + + @Override + @Transactional + public void sendTempPassword(String name, String email, String tempPassword) { + SimpleMailMessage message = new SimpleMailMessage(); + + message.setTo(email); + message.setFrom(senderEmail); + message.setSubject("[ReciPick] 임시 비밀번호 발급"); + message.setText(String.format(""" + 안녕하세요. %s님! 임시 비밀번호가 발급되었습니다. + + 임시 비밀번호: %s + + 보안을 위해 로그인 후 반드시 비밀번호를 변경해 주세요. + """, name, tempPassword)); + + mailSender.send(message); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java b/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java index 9109d98..cfb1abd 100644 --- a/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java +++ b/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java @@ -1,9 +1,13 @@ package moja.refrigerator.service.user; +import moja.refrigerator.dto.user.request.PasswordResetRequest; +import moja.refrigerator.dto.user.request.PasswordUpdateRequest; import moja.refrigerator.dto.user.request.UserCreateRequest; import moja.refrigerator.dto.user.request.UserUpdateRequest; public interface UserService { void createUser(UserCreateRequest request); void updateUser(UserUpdateRequest request); + void resetPassword(PasswordResetRequest request); + void updatePassword(PasswordUpdateRequest request); } diff --git a/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java b/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java index 5309804..7329781 100644 --- a/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java +++ b/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java @@ -1,11 +1,14 @@ package moja.refrigerator.service.user; +import lombok.RequiredArgsConstructor; import moja.refrigerator.aggregate.user.User; +import moja.refrigerator.dto.user.request.PasswordResetRequest; +import moja.refrigerator.dto.user.request.PasswordUpdateRequest; import moja.refrigerator.dto.user.request.UserCreateRequest; import moja.refrigerator.dto.user.request.UserUpdateRequest; import moja.refrigerator.exception.user.DuplicateUserException; -import moja.refrigerator.repository.user.TokenBlacklistRepository; import moja.refrigerator.repository.user.UserRepository; +import moja.refrigerator.service.email.EmailService; import org.modelmapper.ModelMapper; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -16,18 +19,15 @@ import java.util.ArrayList; import java.util.List; +import java.util.UUID; @Service +@RequiredArgsConstructor public class UserServiceImpl implements UserService { private final UserRepository userRepository; private final BCryptPasswordEncoder passwordEncoder; private final ModelMapper modelMapper; - - public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder, ModelMapper modelMapper) { - this.userRepository = userRepository; - this.passwordEncoder = passwordEncoder; - this.modelMapper = modelMapper; - } + private final EmailService emailService; @Override @Transactional @@ -74,6 +74,46 @@ public void updateUser(UserUpdateRequest request) { } } + @Override + @Transactional + public void resetPassword(PasswordResetRequest request) { + // 이메일로 사용자 찾기 + User user = userRepository.findByUserEmail(request.getUserEmail()) + .orElseThrow(() -> new UsernameNotFoundException("해당 이메일로 가입된 계정이 없습니다.")); + + // 임시 비밀번호 생성 + String tempPassword = UUID.randomUUID().toString().substring(0, 12); + + try { + // 이메일 발송 + emailService.sendTempPassword(user.getUserNickname(), user.getUserEmail(), tempPassword); + // 임시 비밀번호로 업데이트 + user.setUserPw(passwordEncoder.encode(tempPassword)); + } catch (Exception e) { + System.out.println("Detailed error: " + e.getMessage()); + throw new RuntimeException("이메일 발송에 실패했습니다."); + } + + } + + @Override + @Transactional + public void updatePassword(PasswordUpdateRequest request) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String userId = authentication.getName(); + + User user = userRepository.findByUserId(userId) + .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); + + // 기존 비밀번호 검증 + if (!passwordEncoder.matches(request.getCurrentPw(), user.getUserPw())) { + throw new IllegalArgumentException("기존 비밀번호가 일치하지 않습니다."); + } + + // 새 비밀번호 암호화 후 저장 + user.setUserPw(passwordEncoder.encode(request.getNewPw())); + } + private void checkDuplicateUser(UserCreateRequest request) { List errors = new ArrayList<>(); if (userRepository.existsByUserId(request.getUserId())) {