From eb6b9830ce73721f069e3a095f705019def66f18 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Sun, 31 May 2026 18:46:42 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=20(PUT=20/api/auth/pass?= =?UTF-8?q?word)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 현재 비번 검증 후 BCrypt 교체. 소셜 계정/동일 비번 거부. Co-Authored-By: Claude Opus 4.8 --- .../web/auth/controller/AuthController.java | 9 ++++++++ .../web/auth/dto/PasswordChangeRequest.java | 19 +++++++++++++++ .../com/sb/web/auth/mapper/MemberMapper.java | 2 ++ .../com/sb/web/auth/service/AuthService.java | 23 +++++++++++++++++++ .../com/sb/web/common/config/WebConfig.java | 3 ++- src/main/resources/mapper/MemberMapper.xml | 4 ++++ 6 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/sb/web/auth/dto/PasswordChangeRequest.java diff --git a/src/main/java/com/sb/web/auth/controller/AuthController.java b/src/main/java/com/sb/web/auth/controller/AuthController.java index f217512..eaeb748 100644 --- a/src/main/java/com/sb/web/auth/controller/AuthController.java +++ b/src/main/java/com/sb/web/auth/controller/AuthController.java @@ -3,6 +3,7 @@ package com.sb.web.auth.controller; import com.sb.web.auth.dto.LoginRequest; import com.sb.web.auth.dto.LoginResponse; import com.sb.web.auth.dto.MemberResponse; +import com.sb.web.auth.dto.PasswordChangeRequest; import com.sb.web.auth.dto.SessionUser; import com.sb.web.auth.dto.SignupRequest; import com.sb.web.auth.service.AuthService; @@ -48,4 +49,12 @@ public class AuthController { public SessionUser me(@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) { return current; } + + @PutMapping("/password") + public ResponseEntity changePassword( + @Valid @RequestBody PasswordChangeRequest req, + @RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) { + authService.changePassword(current.getId(), req); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/sb/web/auth/dto/PasswordChangeRequest.java b/src/main/java/com/sb/web/auth/dto/PasswordChangeRequest.java new file mode 100644 index 0000000..934cac3 --- /dev/null +++ b/src/main/java/com/sb/web/auth/dto/PasswordChangeRequest.java @@ -0,0 +1,19 @@ +package com.sb.web.auth.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * 비밀번호 변경 요청 (로그인 사용자 본인). + */ +@Data +public class PasswordChangeRequest { + + @NotBlank(message = "현재 비밀번호를 입력하세요.") + private String currentPassword; + + @NotBlank(message = "새 비밀번호를 입력하세요.") + @Size(min = 8, max = 64, message = "비밀번호는 8~64자여야 합니다.") + private String newPassword; +} diff --git a/src/main/java/com/sb/web/auth/mapper/MemberMapper.java b/src/main/java/com/sb/web/auth/mapper/MemberMapper.java index 56406d0..a185f7b 100644 --- a/src/main/java/com/sb/web/auth/mapper/MemberMapper.java +++ b/src/main/java/com/sb/web/auth/mapper/MemberMapper.java @@ -20,4 +20,6 @@ public interface MemberMapper { int countByLoginId(@Param("loginId") String loginId); int insert(Member member); + + int updatePassword(@Param("id") Long id, @Param("password") String password); } diff --git a/src/main/java/com/sb/web/auth/service/AuthService.java b/src/main/java/com/sb/web/auth/service/AuthService.java index bc0c210..33d528a 100644 --- a/src/main/java/com/sb/web/auth/service/AuthService.java +++ b/src/main/java/com/sb/web/auth/service/AuthService.java @@ -4,6 +4,7 @@ import com.sb.web.auth.domain.Member; import com.sb.web.auth.dto.LoginRequest; import com.sb.web.auth.dto.LoginResponse; import com.sb.web.auth.dto.MemberResponse; +import com.sb.web.auth.dto.PasswordChangeRequest; import com.sb.web.auth.dto.SessionUser; import com.sb.web.auth.dto.SignupRequest; import com.sb.web.common.exception.ApiException; @@ -99,4 +100,26 @@ public class AuthService { redisTemplate.delete(SESSION_PREFIX + token); } } + + /** + * 비밀번호 변경 (본인). 현재 비밀번호 검증 후 새 비밀번호로 교체. + */ + @Transactional + public void changePassword(Long memberId, PasswordChangeRequest req) { + Member member = memberMapper.findById(memberId); + if (member == null) { + throw new ApiException(HttpStatus.NOT_FOUND, "회원을 찾을 수 없습니다."); + } + if (!"LOCAL".equals(member.getProvider()) || member.getPassword() == null) { + throw new ApiException(HttpStatus.BAD_REQUEST, "소셜 로그인 계정은 비밀번호를 변경할 수 없습니다."); + } + if (!passwordEncoder.matches(req.getCurrentPassword(), member.getPassword())) { + throw new ApiException(HttpStatus.BAD_REQUEST, "현재 비밀번호가 올바르지 않습니다."); + } + if (passwordEncoder.matches(req.getNewPassword(), member.getPassword())) { + throw new ApiException(HttpStatus.BAD_REQUEST, "새 비밀번호가 기존 비밀번호와 동일합니다."); + } + memberMapper.updatePassword(memberId, passwordEncoder.encode(req.getNewPassword())); + log.info("[password] changed for {}", member.getLoginId()); + } } diff --git a/src/main/java/com/sb/web/common/config/WebConfig.java b/src/main/java/com/sb/web/common/config/WebConfig.java index 5e25459..f179ada 100644 --- a/src/main/java/com/sb/web/common/config/WebConfig.java +++ b/src/main/java/com/sb/web/common/config/WebConfig.java @@ -23,7 +23,8 @@ public class WebConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { // 인증: 로그인 필요한 경로 (관리자 경로 포함 — SessionUser 세팅용으로 먼저 실행) registry.addInterceptor(authInterceptor) - .addPathPatterns("/api/auth/me", "/api/auth/logout", "/api/board/**", "/api/admin/**", "/api/account/**"); + .addPathPatterns("/api/auth/me", "/api/auth/logout", "/api/auth/password", + "/api/board/**", "/api/admin/**", "/api/account/**"); // 인가: 관리자 전용 경로 (authInterceptor 다음에 실행되어 role 검사) registry.addInterceptor(adminInterceptor) .addPathPatterns("/api/admin/**"); diff --git a/src/main/resources/mapper/MemberMapper.xml b/src/main/resources/mapper/MemberMapper.xml index de5fe83..6a889c7 100644 --- a/src/main/resources/mapper/MemberMapper.xml +++ b/src/main/resources/mapper/MemberMapper.xml @@ -46,4 +46,8 @@ #{provider}, #{providerId}, #{role}, #{status}, NOW(), NOW()) + + UPDATE member SET password = #{password}, updated_at = NOW() WHERE id = #{id} + +