From beffdb6c6c7ad1288fb0791ad3ae555b00bccff1 Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 10:46:24 +0900 Subject: [PATCH 01/15] [Feat] #5 MailService --- .../domain/auth/service/MailService.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java new file mode 100644 index 0000000..984a2a4 --- /dev/null +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java @@ -0,0 +1,117 @@ +package com.WhoIsRoom.WhoIs_Server.domain.auth.service; + +import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomAuthenticationException; +import com.WhoIsRoom.WhoIs_Server.domain.user.repository.UserRepository; +import com.WhoIsRoom.WhoIs_Server.global.common.redis.RedisService; +import com.WhoIsRoom.WhoIs_Server.global.common.response.ErrorCode; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.MailException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +import java.time.Duration; +import java.util.Random; + +@Slf4j +@Transactional +@Service +@RequiredArgsConstructor +public class MailService { + + private static final long VERIFICATION_CODE_EXPIRY_MINUTES = 5; + + private static final long VERIFIED_TTL_SECONDS = 1800; // 30분 + + private static final String EMAIL_KEY_PREFIX = "auth:email:"; + + private final JavaMailSender javaMailSender; + + private final SpringTemplateEngine templateEngine; + + private final UserRepository userRepository; + + private final RedisService redisService; + + public void sendMail(MailRequest request) { + if (userRepository.findByEmail(request.getEmail()).isPresent()) { + throw new CustomAuthenticationException(ErrorCode.USER_DUPLICATE_EMAIL); + } + + String authCode = createCode(); + MimeMessage mimeMessage = createEmailMessage(request.getEmail(), authCode); + + try { + javaMailSender.send(mimeMessage); + + String key = EMAIL_KEY_PREFIX + request.getEmail(); + redisService.setValues(key, authCode, Duration.ofMinutes(VERIFICATION_CODE_EXPIRY_MINUTES)); + } catch (MailException e) { //JavaMailSender의 전송과정에서 오류 발생 시 + throw new CustomAuthenticationException(ErrorCode.MAIL_SEND_FAILED); + } + } + + // 인증 번호 6자리를 구현하는 메서드 + public String createCode() { + Random random = new Random(); + StringBuilder key = new StringBuilder(); + + for (int i = 0; i < 6; i++) { + key.append(random.nextInt(10)); // 0~9 숫자 + } + + return key.toString(); + } + + private MimeMessage createEmailMessage(String recipient, String authCode) { + try { + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); + + mimeMessageHelper.setTo(recipient); + mimeMessageHelper.setSubject("[BARO] 이메일 인증을 위한 인증 코드 발송"); + mimeMessageHelper.setText(setContext(authCode), true); + + return mimeMessage; + } catch (MessagingException e) { // SMTP 전송 오류, 포맷 오류 발생 시 + throw new CustomAuthenticationException(ErrorCode.MAIL_SEND_FAILED); + } + } + + public void checkAuthCode(CodeCheckRequest request) { + String storedCode = getStoredCode(request.getEmail()); + if (storedCode == null) { + throw new CustomAuthenticationException(ErrorCode.EXPIRED_EMAIL_CODE); + } + + // 인증 번호가 이미 인증된 상태인 경우 그냥 리턴 + if ("VERIFIED".equals(getStoredCode(request.getEmail()))){return;}; + + // 입력 코드와 Redis 코드가 다르면 에러 + if (!String.valueOf(request.getAuthCode()).equals(storedCode)) { + throw new CustomAuthenticationException(ErrorCode.INVALID_EMAIL_CODE); + } + // 인증 성공: 값 변경 + TTL 재설정 + redisService.setValues(EMAIL_KEY_PREFIX + request.getEmail(), "VERIFIED", Duration.ofSeconds(VERIFIED_TTL_SECONDS)); + } + + public String getStoredCode(String email) { + String key = EMAIL_KEY_PREFIX + email; + return redisService.getValues(key); + } + + // thymeleaf를 통한 html 적용 + public String setContext(String authCode) { + Context context = new Context(); + context.setVariable("code", authCode); + return templateEngine.process("authCode-email.html", context); + } +} + + From 2ae20e6c6b886a8325b084254d3945b90e14d9dc Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 10:47:52 +0900 Subject: [PATCH 02/15] [Feat] #5 MailRequest --- .../domain/auth/dto/request/MailRequest.java | 13 +++++++++++++ .../domain/auth/service/MailService.java | 1 + 2 files changed, 14 insertions(+) create mode 100644 src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/MailRequest.java diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/MailRequest.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/MailRequest.java new file mode 100644 index 0000000..f4ce932 --- /dev/null +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/MailRequest.java @@ -0,0 +1,13 @@ +package com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class MailRequest { + private String email; +} \ No newline at end of file diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java index 984a2a4..be3b588 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java @@ -1,5 +1,6 @@ package com.WhoIsRoom.WhoIs_Server.domain.auth.service; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomAuthenticationException; import com.WhoIsRoom.WhoIs_Server.domain.user.repository.UserRepository; import com.WhoIsRoom.WhoIs_Server.global.common.redis.RedisService; From c7e82f59c38a5088022e21cfc48a3ea49aa69c2c Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 10:49:17 +0900 Subject: [PATCH 03/15] [Feat] #5 CodeCheckRequest --- .../domain/auth/dto/request/CodeCheckRequest.java | 15 +++++++++++++++ .../domain/auth/service/MailService.java | 1 + 2 files changed, 16 insertions(+) create mode 100644 src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java new file mode 100644 index 0000000..a93c232 --- /dev/null +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/CodeCheckRequest.java @@ -0,0 +1,15 @@ +package com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class CodeCheckRequest { + private String email; + private String authCode; +} + diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java index be3b588..1c3fecc 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java @@ -1,5 +1,6 @@ package com.WhoIsRoom.WhoIs_Server.domain.auth.service; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.CodeCheckRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomAuthenticationException; import com.WhoIsRoom.WhoIs_Server.domain.user.repository.UserRepository; From 5c2d212fb9f8d83cc800e5768c698f42ed1cbc6c Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 10:53:59 +0900 Subject: [PATCH 04/15] [Feat] #5 Authcode-email.html --- .../domain/auth/service/MailService.java | 2 +- .../resources/templates/AuthCode-email.html | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/templates/AuthCode-email.html diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java index 1c3fecc..d2cfec3 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java @@ -112,7 +112,7 @@ public String getStoredCode(String email) { public String setContext(String authCode) { Context context = new Context(); context.setVariable("code", authCode); - return templateEngine.process("authCode-email.html", context); + return templateEngine.process("AuthCode-email.html", context); } } diff --git a/src/main/resources/templates/AuthCode-email.html b/src/main/resources/templates/AuthCode-email.html new file mode 100644 index 0000000..d83655e --- /dev/null +++ b/src/main/resources/templates/AuthCode-email.html @@ -0,0 +1,20 @@ + + + + +
+

안녕하세요.

+

동아리에 누가 있는지 알려주는 동방에누구입니다.

+
+

아래 코드를 회원가입 창으로 돌아가 입력해주세요.

+
+ +
+

회원가입 인증 번호 입니다.

+
+
+
+
+ + + \ No newline at end of file From 71ff36378e48a1fba2e0a9000ee313ed4fc5f279 Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 10:58:09 +0900 Subject: [PATCH 05/15] =?UTF-8?q?[Feat]=20#5=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A1=9C=EC=A7=81=EC=97=90=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WhoIs_Server/domain/auth/service/MailService.java | 11 ++++++----- .../WhoIs_Server/domain/user/service/UserService.java | 5 +++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java index d2cfec3..9b8b14d 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java @@ -4,6 +4,7 @@ import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomAuthenticationException; import com.WhoIsRoom.WhoIs_Server.domain.user.repository.UserRepository; +import com.WhoIsRoom.WhoIs_Server.global.common.exception.BusinessException; import com.WhoIsRoom.WhoIs_Server.global.common.redis.RedisService; import com.WhoIsRoom.WhoIs_Server.global.common.response.ErrorCode; import jakarta.mail.MessagingException; @@ -43,7 +44,7 @@ public class MailService { public void sendMail(MailRequest request) { if (userRepository.findByEmail(request.getEmail()).isPresent()) { - throw new CustomAuthenticationException(ErrorCode.USER_DUPLICATE_EMAIL); + throw new BusinessException(ErrorCode.USER_DUPLICATE_EMAIL); } String authCode = createCode(); @@ -55,7 +56,7 @@ public void sendMail(MailRequest request) { String key = EMAIL_KEY_PREFIX + request.getEmail(); redisService.setValues(key, authCode, Duration.ofMinutes(VERIFICATION_CODE_EXPIRY_MINUTES)); } catch (MailException e) { //JavaMailSender의 전송과정에서 오류 발생 시 - throw new CustomAuthenticationException(ErrorCode.MAIL_SEND_FAILED); + throw new BusinessException(ErrorCode.MAIL_SEND_FAILED); } } @@ -82,14 +83,14 @@ private MimeMessage createEmailMessage(String recipient, String authCode) { return mimeMessage; } catch (MessagingException e) { // SMTP 전송 오류, 포맷 오류 발생 시 - throw new CustomAuthenticationException(ErrorCode.MAIL_SEND_FAILED); + throw new BusinessException(ErrorCode.MAIL_SEND_FAILED); } } public void checkAuthCode(CodeCheckRequest request) { String storedCode = getStoredCode(request.getEmail()); if (storedCode == null) { - throw new CustomAuthenticationException(ErrorCode.EXPIRED_EMAIL_CODE); + throw new BusinessException(ErrorCode.EXPIRED_EMAIL_CODE); } // 인증 번호가 이미 인증된 상태인 경우 그냥 리턴 @@ -97,7 +98,7 @@ public void checkAuthCode(CodeCheckRequest request) { // 입력 코드와 Redis 코드가 다르면 에러 if (!String.valueOf(request.getAuthCode()).equals(storedCode)) { - throw new CustomAuthenticationException(ErrorCode.INVALID_EMAIL_CODE); + throw new BusinessException(ErrorCode.INVALID_EMAIL_CODE); } // 인증 성공: 값 변경 + TTL 재설정 redisService.setValues(EMAIL_KEY_PREFIX + request.getEmail(), "VERIFIED", Duration.ofSeconds(VERIFIED_TTL_SECONDS)); diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java index ae0cbe9..765583d 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java @@ -1,5 +1,6 @@ package com.WhoIsRoom.WhoIs_Server.domain.user.service; +import com.WhoIsRoom.WhoIs_Server.domain.auth.service.MailService; import com.WhoIsRoom.WhoIs_Server.domain.user.dto.request.SignupRequest; import com.WhoIsRoom.WhoIs_Server.domain.user.model.Role; import com.WhoIsRoom.WhoIs_Server.domain.user.model.User; @@ -18,6 +19,7 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final MailService mailService; @Transactional public void signUp(SignupRequest request) { @@ -27,6 +29,9 @@ public void signUp(SignupRequest request) { if (userRepository.existsByNickName(request.getNickName())) { throw new BusinessException(ErrorCode.USER_DUPLICATE_NICKNAME); } + if (!"VERIFIED".equals(mailService.getStoredCode(request.getEmail()))){ + throw new BusinessException(ErrorCode.AUTHCODE_UNAUTHORIZED); + } User user = User.builder() .email(request.getEmail()) From 0841db5372e1e271a4ac25035b1cbe96195ee4b5 Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 11:01:32 +0900 Subject: [PATCH 06/15] =?UTF-8?q?[Feat]=20#5=20AuthController=EC=97=90=20?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java index 5cd7dd2..f58eb49 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java @@ -1,15 +1,16 @@ package com.WhoIsRoom.WhoIs_Server.domain.auth.controller; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.CodeCheckRequest; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.JwtService; +import com.WhoIsRoom.WhoIs_Server.domain.auth.service.MailService; import com.WhoIsRoom.WhoIs_Server.global.common.response.BaseResponse; +import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @Slf4j @RestController @@ -18,6 +19,7 @@ public class AuthController { private final JwtService jwtService; + private final MailService mailService; @PostMapping("/logout") public BaseResponse logout(HttpServletRequest request){ @@ -30,4 +32,16 @@ public BaseResponse reissueTokens(HttpServletRequest request, HttpServletR jwtService.reissueTokens(request, response); return BaseResponse.ok(null); } + + @PostMapping("/email/send") + public BaseResponse sendAuthCodeMail(@RequestBody MailRequest request) { + mailService.sendMail(request); + return BaseResponse.ok(null); + } + + @PostMapping("/email/validation") + public BaseResponse checkAuthCode(@RequestBody CodeCheckRequest request) { + mailService.checkAuthCode(request); + return BaseResponse.ok(null); + } } From a9f88af85ea730c4d690d997800da4c7e196dafb Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 12:37:30 +0900 Subject: [PATCH 07/15] =?UTF-8?q?[Feat]=20#5=20UserService=EC=97=90=20?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/service/UserService.java | 7 +++++++ src/main/resources/application.yml | 20 +++++++++---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java index 765583d..28c6f37 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java @@ -41,4 +41,11 @@ public void signUp(SignupRequest request) { .build(); userRepository.save(user); } + + @Transactional + public void updateMyPassword(Long userId, String password) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + user.setPassword(passwordEncoder.encode(password)); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b24d5f6..032cc76 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -62,16 +62,16 @@ spring: web: resources: add-mappings: false -# mail: -# host: smtp.gmail.com -# port: 587 -# username: ${MAIL_USERNAME} -# password: ${MAIL_PASSWORD} -# properties: -# mail.smtp.auth: true -# mail.smtp.timeout: 5000 -# mail.smtp.starttls.enable: true -# + mail: + host: smtp.gmail.com + port: 587 + username: ${MAIL_ADDRESS} + password: ${MAIL_PASSWORD} + properties: + mail.smtp.auth: true + mail.smtp.timeout: 5000 + mail.smtp.starttls.enable: true + jwt: secret: ${JWT_SECRET_KEY} access: From e6b492856f9e6c9b12902bc68e16dba44e93dab9 Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 14:14:11 +0900 Subject: [PATCH 08/15] =?UTF-8?q?[Refact]=20#5=20yml=EC=9D=84=20.env=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++++ src/main/resources/application.yml | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c2065bc..ce9b259 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ out/ ### VS Code ### .vscode/ + +### .env file ### +*.env + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 032cc76..ce5040c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,7 +4,7 @@ spring: profiles: group: local : local-db, local-port, common - prod: prod-db, prod-port,common + prod: prod-db, prod-port, common active: local --- # 로컬용 DB @@ -59,6 +59,7 @@ spring: config: activate: on-profile: common + import: optional:file:.env[.properties] web: resources: add-mappings: false From bbe846461bcc1d7b9bc99dbebe5232e952683fab Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 15:04:55 +0900 Subject: [PATCH 09/15] =?UTF-8?q?[Feat]=20#5=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 8 +++ .../domain/auth/service/MailService.java | 53 ++++++++++++++++++- .../domain/user/service/UserService.java | 8 +-- .../resources/templates/Password-email.html | 20 +++++++ 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/templates/Password-email.html diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java index f58eb49..bc37425 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java @@ -4,6 +4,7 @@ import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.JwtService; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.MailService; +import com.WhoIsRoom.WhoIs_Server.domain.user.service.UserService; import com.WhoIsRoom.WhoIs_Server.global.common.response.BaseResponse; import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletRequest; @@ -20,6 +21,7 @@ public class AuthController { private final JwtService jwtService; private final MailService mailService; + private final UserService userService; @PostMapping("/logout") public BaseResponse logout(HttpServletRequest request){ @@ -44,4 +46,10 @@ public BaseResponse checkAuthCode(@RequestBody CodeCheckRequest request) { mailService.checkAuthCode(request); return BaseResponse.ok(null); } + + @PostMapping("/email/find-password") + public BaseResponse findPassword(@RequestBody MailRequest request) { + userService.updateMyPassword(request); + return BaseResponse.ok(null); + } } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java index 9b8b14d..6d913b9 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/MailService.java @@ -4,6 +4,7 @@ import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomAuthenticationException; import com.WhoIsRoom.WhoIs_Server.domain.user.repository.UserRepository; +import com.WhoIsRoom.WhoIs_Server.domain.user.service.UserService; import com.WhoIsRoom.WhoIs_Server.global.common.exception.BusinessException; import com.WhoIsRoom.WhoIs_Server.global.common.redis.RedisService; import com.WhoIsRoom.WhoIs_Server.global.common.response.ErrorCode; @@ -60,6 +61,17 @@ public void sendMail(MailRequest request) { } } + public String sendPasswordMail(MailRequest request) { + String password = createNewPassword(); + MimeMessage mimeMessage = createPasswordEmailMessage(request.getEmail(), password); + try { + javaMailSender.send(mimeMessage); + } catch (MailException e) { //JavaMailSender의 전송과정에서 오류 발생 시 + throw new BusinessException(ErrorCode.MAIL_SEND_FAILED); + } + return password; + } + // 인증 번호 6자리를 구현하는 메서드 public String createCode() { Random random = new Random(); @@ -72,13 +84,30 @@ public String createCode() { return key.toString(); } + // 임시 비밀번호를 구현하는 메서드 + public String createNewPassword() { + Random random = new Random(); + StringBuffer key = new StringBuffer(); + + for (int i = 0; i < 8; i++) { + int index = random.nextInt(4); + + switch (index) { + case 0: key.append((char) ((int) random.nextInt(26) + 97)); break; + case 1: key.append((char) ((int) random.nextInt(26) + 65)); break; + default: key.append(random.nextInt(9)); + } + } + return key.toString(); + } + private MimeMessage createEmailMessage(String recipient, String authCode) { try { MimeMessage mimeMessage = javaMailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); mimeMessageHelper.setTo(recipient); - mimeMessageHelper.setSubject("[BARO] 이메일 인증을 위한 인증 코드 발송"); + mimeMessageHelper.setSubject("[동방에누구] 이메일 인증을 위한 인증 코드 발송"); mimeMessageHelper.setText(setContext(authCode), true); return mimeMessage; @@ -87,6 +116,21 @@ private MimeMessage createEmailMessage(String recipient, String authCode) { } } + private MimeMessage createPasswordEmailMessage(String recipient, String password) { + try { + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); + + mimeMessageHelper.setTo(recipient); + mimeMessageHelper.setSubject("[동방에누구] 임시 비밀번호 발송"); + mimeMessageHelper.setText(setPasswordContext(password), true); + + return mimeMessage; + } catch (MessagingException e) { // SMTP 전송 오류, 포맷 오류 발생 시 + throw new BusinessException(ErrorCode.MAIL_SEND_FAILED); + } + } + public void checkAuthCode(CodeCheckRequest request) { String storedCode = getStoredCode(request.getEmail()); if (storedCode == null) { @@ -115,6 +159,13 @@ public String setContext(String authCode) { context.setVariable("code", authCode); return templateEngine.process("AuthCode-email.html", context); } + + // thymeleaf를 통한 html 적용 + public String setPasswordContext(String password) { + Context context = new Context(); + context.setVariable("password", password); + return templateEngine.process("Password-email.html", context); + } } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java index 28c6f37..cd89bd6 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java @@ -1,5 +1,6 @@ package com.WhoIsRoom.WhoIs_Server.domain.user.service; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.MailService; import com.WhoIsRoom.WhoIs_Server.domain.user.dto.request.SignupRequest; import com.WhoIsRoom.WhoIs_Server.domain.user.model.Role; @@ -43,9 +44,10 @@ public void signUp(SignupRequest request) { } @Transactional - public void updateMyPassword(Long userId, String password) { - User user = userRepository.findById(userId) + public void updateMyPassword(MailRequest request) { + User user = userRepository.findByEmail(request.getEmail()) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); - user.setPassword(passwordEncoder.encode(password)); + String newPassword = mailService.sendPasswordMail(request); + user.setPassword(passwordEncoder.encode(newPassword)); } } diff --git a/src/main/resources/templates/Password-email.html b/src/main/resources/templates/Password-email.html new file mode 100644 index 0000000..b3f58ed --- /dev/null +++ b/src/main/resources/templates/Password-email.html @@ -0,0 +1,20 @@ + + + + +
+

안녕하세요.

+

동아리에 누가 있는지 알려주는 동방에누구입니다.

+
+

임시 비밀번호가 다음과 같이 변경되었습니다.

+
+ +
+

임시 비밀번호 입니다.

+
+
+
+
+ + + \ No newline at end of file From 4c13ee91b94bb9a0d55283ab60874817889a3eb9 Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 15:09:50 +0900 Subject: [PATCH 10/15] [Feat] #5 RefreshTokenRequest --- .../domain/auth/dto/request/RefreshTokenRequest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/RefreshTokenRequest.java diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/RefreshTokenRequest.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/RefreshTokenRequest.java new file mode 100644 index 0000000..7002cbc --- /dev/null +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/RefreshTokenRequest.java @@ -0,0 +1,11 @@ +package com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RefreshTokenRequest { + private String refreshToken; +} From 7630c3f559d05661623bd2dbb25a1f5199d241b9 Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 15:14:26 +0900 Subject: [PATCH 11/15] =?UTF-8?q?[Refact]=20#5=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=95=84=EC=9B=83&=EC=9E=AC=EB=B0=9C=EA=B8=89=20refresh=20Requ?= =?UTF-8?q?est=EB=A1=9C=20=EB=B0=9B=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/AuthController.java | 9 +++++---- .../WhoIs_Server/domain/auth/service/JwtService.java | 12 +++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java index bc37425..e320384 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java @@ -2,6 +2,7 @@ import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.CodeCheckRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.RefreshTokenRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.JwtService; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.MailService; import com.WhoIsRoom.WhoIs_Server.domain.user.service.UserService; @@ -24,14 +25,14 @@ public class AuthController { private final UserService userService; @PostMapping("/logout") - public BaseResponse logout(HttpServletRequest request){ - jwtService.logout(request); + public BaseResponse logout(HttpServletRequest request, @RequestBody RefreshTokenRequest tokenRequest){ + jwtService.logout(request, tokenRequest); return BaseResponse.ok(null); } @PostMapping("/reissue") - public BaseResponse reissueTokens(HttpServletRequest request, HttpServletResponse response) { - jwtService.reissueTokens(request, response); + public BaseResponse reissueTokens(HttpServletRequest request, HttpServletResponse response, @RequestBody RefreshTokenRequest tokenRequest) { + jwtService.reissueTokens(request, response, tokenRequest); return BaseResponse.ok(null); } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java index a20d69b..8fe3ebd 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java @@ -1,5 +1,6 @@ package com.WhoIsRoom.WhoIs_Server.domain.auth.service; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.RefreshTokenRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomAuthenticationException; import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomJwtException; import com.WhoIsRoom.WhoIs_Server.domain.auth.util.JwtUtil; @@ -38,21 +39,18 @@ public class JwtService { private final RedisService redisService; private final JwtUtil jwtUtil; - public void logout(HttpServletRequest request) { + public void logout(HttpServletRequest request, RefreshTokenRequest tokenRequest) { String accessToken = jwtUtil.extractAccessToken(request) .orElseThrow(() -> new CustomAuthenticationException(ErrorCode.SECURITY_INVALID_ACCESS_TOKEN)); - String refreshToken = jwtUtil.extractRefreshToken(request) - .orElseThrow(() -> new CustomAuthenticationException(ErrorCode.SECURITY_INVALID_REFRESH_TOKEN)); + String refreshToken = tokenRequest.getRefreshToken(); deleteRefreshToken(refreshToken); //access token blacklist 처리 -> 로그아웃한 사용자가 요청 시 access token이 redis에 존재하면 jwtAuthenticationFilter에서 인증처리 거부 invalidAccessToken(accessToken); } - public void reissueTokens(HttpServletRequest request, HttpServletResponse response) { - String refreshToken = jwtUtil.extractRefreshToken(request) - .orElseThrow(() -> new CustomAuthenticationException(ErrorCode.SECURITY_INVALID_REFRESH_TOKEN)); - + public void reissueTokens(HttpServletRequest request, HttpServletResponse response, RefreshTokenRequest tokenRequest) { + String refreshToken = tokenRequest.getRefreshToken(); jwtUtil.validateToken(refreshToken); if (!"refresh".equals(jwtUtil.getTokenType(refreshToken))) { throw new CustomJwtException(ErrorCode.INVALID_TOKEN_TYPE); From a210962598d19004030a1b6ebaa93a935ca52239 Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 15:17:29 +0900 Subject: [PATCH 12/15] [Feat] #5 ReissueResponse --- .../domain/auth/controller/AuthController.java | 7 +++++-- .../domain/auth/dto/response/ReissueResponse.java | 11 +++++++++++ .../success/CustomAuthenticationSuccessHandler.java | 2 -- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/response/ReissueResponse.java diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java index e320384..c0aebcc 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java @@ -25,13 +25,16 @@ public class AuthController { private final UserService userService; @PostMapping("/logout") - public BaseResponse logout(HttpServletRequest request, @RequestBody RefreshTokenRequest tokenRequest){ + public BaseResponse logout(HttpServletRequest request, + @RequestBody RefreshTokenRequest tokenRequest){ jwtService.logout(request, tokenRequest); return BaseResponse.ok(null); } @PostMapping("/reissue") - public BaseResponse reissueTokens(HttpServletRequest request, HttpServletResponse response, @RequestBody RefreshTokenRequest tokenRequest) { + public BaseResponse reissueTokens(HttpServletRequest request, + HttpServletResponse response, + @RequestBody RefreshTokenRequest tokenRequest) { jwtService.reissueTokens(request, response, tokenRequest); return BaseResponse.ok(null); } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/response/ReissueResponse.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/response/ReissueResponse.java new file mode 100644 index 0000000..2bb775a --- /dev/null +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/response/ReissueResponse.java @@ -0,0 +1,11 @@ +package com.WhoIsRoom.WhoIs_Server.domain.auth.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ReissueResponse { + private String accessToken; + private String refreshToken; +} diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java index 24cd7f8..fe2be56 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/handler/success/CustomAuthenticationSuccessHandler.java @@ -47,8 +47,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo jwtService.storeRefreshToken(refreshToken); log.info("[CustomAuthenticationSuccessHandler], refreshToken={}", refreshToken); - jwtService.sendTokens(response, accessToken, refreshToken); - LoginResponse data = LoginResponse.builder() .accessToken(accessToken) .refreshToken(refreshToken) From a7aaf442f0215dc9597963ac37502d8e47ea448d Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Mon, 29 Sep 2025 15:32:04 +0900 Subject: [PATCH 13/15] =?UTF-8?q?[Refact]=20#5=20ReissueToken=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20Json=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 9 +++--- .../domain/auth/service/JwtService.java | 29 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java index c0aebcc..0f2ca46 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java @@ -3,6 +3,7 @@ import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.CodeCheckRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.RefreshTokenRequest; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.response.ReissueResponse; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.JwtService; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.MailService; import com.WhoIsRoom.WhoIs_Server.domain.user.service.UserService; @@ -32,11 +33,9 @@ public BaseResponse logout(HttpServletRequest request, } @PostMapping("/reissue") - public BaseResponse reissueTokens(HttpServletRequest request, - HttpServletResponse response, - @RequestBody RefreshTokenRequest tokenRequest) { - jwtService.reissueTokens(request, response, tokenRequest); - return BaseResponse.ok(null); + public BaseResponse reissueTokens(@RequestBody RefreshTokenRequest tokenRequest) { + ReissueResponse response = jwtService.reissueTokens(tokenRequest); + return BaseResponse.ok(response); } @PostMapping("/email/send") diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java index 8fe3ebd..35e392e 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/service/JwtService.java @@ -1,18 +1,24 @@ package com.WhoIsRoom.WhoIs_Server.domain.auth.service; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.RefreshTokenRequest; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.response.LoginResponse; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.response.ReissueResponse; import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomAuthenticationException; import com.WhoIsRoom.WhoIs_Server.domain.auth.exception.CustomJwtException; import com.WhoIsRoom.WhoIs_Server.domain.auth.util.JwtUtil; import com.WhoIsRoom.WhoIs_Server.global.common.redis.RedisService; +import com.WhoIsRoom.WhoIs_Server.global.common.response.BaseResponse; import com.WhoIsRoom.WhoIs_Server.global.common.response.ErrorCode; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; +import java.io.IOException; import java.time.Duration; @Slf4j @@ -38,24 +44,30 @@ public class JwtService { private final RedisService redisService; private final JwtUtil jwtUtil; + private final ObjectMapper objectMapper; public void logout(HttpServletRequest request, RefreshTokenRequest tokenRequest) { String accessToken = jwtUtil.extractAccessToken(request) .orElseThrow(() -> new CustomAuthenticationException(ErrorCode.SECURITY_INVALID_ACCESS_TOKEN)); + String refreshToken = tokenRequest.getRefreshToken(); + jwtUtil.validateToken(refreshToken); + if (!"refresh".equals(jwtUtil.getTokenType(refreshToken))) { + throw new CustomJwtException(ErrorCode.INVALID_TOKEN_TYPE); + } deleteRefreshToken(refreshToken); //access token blacklist 처리 -> 로그아웃한 사용자가 요청 시 access token이 redis에 존재하면 jwtAuthenticationFilter에서 인증처리 거부 invalidAccessToken(accessToken); } - public void reissueTokens(HttpServletRequest request, HttpServletResponse response, RefreshTokenRequest tokenRequest) { + public ReissueResponse reissueTokens(RefreshTokenRequest tokenRequest) { String refreshToken = tokenRequest.getRefreshToken(); jwtUtil.validateToken(refreshToken); if (!"refresh".equals(jwtUtil.getTokenType(refreshToken))) { throw new CustomJwtException(ErrorCode.INVALID_TOKEN_TYPE); } - reissueAndSendTokens(response, refreshToken); + return reissueAndSendTokens(refreshToken); } public void checkLogout(String accessToken) { @@ -81,7 +93,7 @@ private void invalidAccessToken(String accessToken) { Duration.ofMillis(ACCESS_TOKEN_EXPIRED_IN)); } - private void reissueAndSendTokens(HttpServletResponse response, String refreshToken) { + private ReissueResponse reissueAndSendTokens(String refreshToken) { // 새로운 Refresh Token 발급 String reissuedAccessToken = jwtUtil.createAccessToken(jwtUtil.getUserId(refreshToken), jwtUtil.getProviderId(refreshToken), jwtUtil.getRole(refreshToken), jwtUtil.getName(refreshToken)); @@ -93,12 +105,9 @@ private void reissueAndSendTokens(HttpServletResponse response, String refreshTo // 기존 Refresh Token 폐기 (DB나 Redis에서 삭제) deleteRefreshToken(refreshToken); - sendTokens(response, reissuedAccessToken, reissuedRefreshToken); - } - - public void sendTokens(HttpServletResponse response, String accessToken, - String refreshToken) { - response.setHeader(ACCESS_HEADER, BEARER_PREFIX + accessToken); - response.setHeader(REFRESH_HEADER, BEARER_PREFIX + refreshToken); + return ReissueResponse.builder() + .accessToken(reissuedAccessToken) + .refreshToken(reissuedRefreshToken) + .build(); } } From d6a5b03051adf843f81f1504042797cee37a958a Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Tue, 30 Sep 2025 16:26:58 +0900 Subject: [PATCH 14/15] =?UTF-8?q?[Refact]=20#5=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=EC=9D=B5=EC=85=89=EC=85=98=20super=EB=A1=9C=20?= =?UTF-8?q?=EC=9B=90=EC=9D=B8=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/common/exception/BusinessException.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/exception/BusinessException.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/exception/BusinessException.java index 939976f..1df98cf 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/exception/BusinessException.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/exception/BusinessException.java @@ -7,7 +7,8 @@ public class BusinessException extends RuntimeException { private final ErrorCode errorCode; - public BusinessException(ErrorCode errorCode) { - this.errorCode = errorCode; + public BusinessException(ErrorCode code) { + super(code.getMessage()); + this.errorCode = code; } -} +} \ No newline at end of file From ee2464496f4dfbef92ce0f1301a2ea61d5d15111 Mon Sep 17 00:00:00 2001 From: Shinjongyun Date: Tue, 30 Sep 2025 16:43:43 +0900 Subject: [PATCH 15/15] =?UTF-8?q?[Feat]=20#5=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/AuthController.java | 13 ++++++++++--- .../domain/auth/dto/request/PasswordRequest.java | 12 ++++++++++++ .../domain/user/service/UserService.java | 14 +++++++++++++- .../global/common/response/ErrorCode.java | 4 ++-- 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/PasswordRequest.java diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java index 0f2ca46..d02073f 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/controller/AuthController.java @@ -2,15 +2,15 @@ import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.CodeCheckRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.PasswordRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.RefreshTokenRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.response.ReissueResponse; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.JwtService; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.MailService; import com.WhoIsRoom.WhoIs_Server.domain.user.service.UserService; +import com.WhoIsRoom.WhoIs_Server.global.common.resolver.CurrentUserId; import com.WhoIsRoom.WhoIs_Server.global.common.response.BaseResponse; -import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @@ -52,7 +52,14 @@ public BaseResponse checkAuthCode(@RequestBody CodeCheckRequest request) { @PostMapping("/email/find-password") public BaseResponse findPassword(@RequestBody MailRequest request) { - userService.updateMyPassword(request); + userService.sendNewPassword(request); + return BaseResponse.ok(null); + } + + @PatchMapping("/password") + public BaseResponse updatePassword(@CurrentUserId Long userId, + @RequestBody PasswordRequest request) { + userService.updateMyPassword(userId, request); return BaseResponse.ok(null); } } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/PasswordRequest.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/PasswordRequest.java new file mode 100644 index 0000000..a02532c --- /dev/null +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/auth/dto/request/PasswordRequest.java @@ -0,0 +1,12 @@ +package com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PasswordRequest { + private String prePassword; + private String newPassword; +} diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java index cd89bd6..7cc4e4d 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/domain/user/service/UserService.java @@ -1,6 +1,7 @@ package com.WhoIsRoom.WhoIs_Server.domain.user.service; import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.MailRequest; +import com.WhoIsRoom.WhoIs_Server.domain.auth.dto.request.PasswordRequest; import com.WhoIsRoom.WhoIs_Server.domain.auth.service.MailService; import com.WhoIsRoom.WhoIs_Server.domain.user.dto.request.SignupRequest; import com.WhoIsRoom.WhoIs_Server.domain.user.model.Role; @@ -44,10 +45,21 @@ public void signUp(SignupRequest request) { } @Transactional - public void updateMyPassword(MailRequest request) { + public void sendNewPassword(MailRequest request) { User user = userRepository.findByEmail(request.getEmail()) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); String newPassword = mailService.sendPasswordMail(request); user.setPassword(passwordEncoder.encode(newPassword)); } + + @Transactional + public void updateMyPassword(Long userId, PasswordRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + if (!passwordEncoder.matches(request.getPrePassword(), user.getPassword())) { + throw new BusinessException(ErrorCode.INVALID_PASSWORD); + } + user.setPassword(passwordEncoder.encode(request.getNewPassword())); + } } diff --git a/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/response/ErrorCode.java b/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/response/ErrorCode.java index 828eb4c..21891ec 100644 --- a/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/response/ErrorCode.java +++ b/src/main/java/com/WhoIsRoom/WhoIs_Server/global/common/response/ErrorCode.java @@ -41,8 +41,8 @@ public enum ErrorCode{ UNSUPPORTED_TOKEN_TYPE(614, HttpStatus.BAD_REQUEST.value(),"지원되지 않는 토큰 형식입니다."), MALFORMED_TOKEN_TYPE(615, HttpStatus.BAD_REQUEST.value(),"인증 토큰이 올바르게 구성되지 않았습니다."), INVALID_SIGNATURE_JWT(616, HttpStatus.BAD_REQUEST.value(), "인증 시그니처가 올바르지 않습니다"), - INVALID_ID_OR_PASSWORD(617, HttpStatus.BAD_REQUEST.value(), "이메일 또는 비밀번호가 올바르지 않습니다."); - + INVALID_ID_OR_PASSWORD(617, HttpStatus.BAD_REQUEST.value(), "이메일 또는 비밀번호가 올바르지 않습니다."), + INVALID_PASSWORD(618, HttpStatus.BAD_REQUEST.value(), "기존 비밀번호가 유효하지 않습니다"); private final int code; private final int httpStatus;