Skip to content

Commit

Permalink
Feat: 마이페이지 중 비밀번호 변경 관련 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
yunhacandy authored Aug 13, 2024
2 parents 765b2ef + 2988ad7 commit 62588c3
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package cotato.growingpain.auth.controller;

import cotato.growingpain.auth.dto.request.ChangePasswordRequest;
import cotato.growingpain.auth.dto.request.CompleteSignupRequest;
import cotato.growingpain.auth.dto.request.LoginRequest;
import cotato.growingpain.auth.dto.request.LogoutRequest;
import cotato.growingpain.auth.dto.response.ChangePasswordResponse;
import cotato.growingpain.auth.dto.request.ResetPasswordRequest;
import cotato.growingpain.auth.dto.response.ResetPasswordResponse;
import cotato.growingpain.auth.service.AuthService;
import cotato.growingpain.common.Response;
import cotato.growingpain.security.jwt.Token;
Expand All @@ -19,6 +21,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
Expand Down Expand Up @@ -74,11 +77,21 @@ public Response<?> logout(@RequestBody LogoutRequest request) {
}

@Operation(summary = "비밀번호 초기화", description = "비밀번호 찾기 및 초기화를 위한 메소드")
@ApiResponse(content = @Content(schema = @Schema(implementation = ChangePasswordResponse.class)))
@PostMapping("/change-password")
@ApiResponse(content = @Content(schema = @Schema(implementation = ResetPasswordResponse.class)))
@PostMapping("/reset-password")
@ResponseStatus(HttpStatus.OK)
public Response<ChangePasswordResponse> changePassword(@RequestBody cotato.growingpain.auth.dto.request.ChangePasswordRequest request){
public Response<ResetPasswordResponse> resetPassword(@RequestBody ResetPasswordRequest request){
log.info("[비밀번호 초기화 컨트롤러]: {}", request.email());
return Response.createSuccess("비밀번호 초기화 완료",authService.changePassword(request));
return Response.createSuccess("비밀번호 초기화 완료",authService.resetPassword(request));
}

@Operation(summary = "비밀번호 변경", description = "로그인된 사용자가 비밀번호를 변경하기 위한 메소드")
@ApiResponse(content = @Content(schema = @Schema(implementation = Response.class)))
@PostMapping("/change-password")
@ResponseStatus(HttpStatus.OK)
public Response<?> changePassword(@RequestBody @Valid ChangePasswordRequest request,
@AuthenticationPrincipal Long memberId) {
authService.changePassword(request, memberId);
return Response.createSuccessWithNoData("비밀번호 변경 완료");
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package cotato.growingpain.auth.dto.request;

import jakarta.validation.constraints.NotBlank;

public record ChangePasswordRequest(
String email

@NotBlank(message = "기존 비밀번호는 필수 입력 항목입니다.")
String currentPassword,

@NotBlank(message = "새 비밀번호는 필수 입력 항목입니다.")
String newPassword
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package cotato.growingpain.auth.dto.request;

public record ResetPasswordRequest(
String email
) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cotato.growingpain.auth.dto.response;

public record ChangePasswordResponse(
public record ResetPasswordResponse(
String password
) {
}
83 changes: 65 additions & 18 deletions src/main/java/cotato/growingpain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import cotato.growingpain.auth.dto.request.CompleteSignupRequest;
import cotato.growingpain.auth.dto.request.LoginRequest;
import cotato.growingpain.auth.dto.request.LogoutRequest;
import cotato.growingpain.auth.dto.response.ChangePasswordResponse;
import cotato.growingpain.auth.dto.request.ResetPasswordRequest;
import cotato.growingpain.auth.dto.response.ResetPasswordResponse;
import cotato.growingpain.auth.repository.BlackListRepository;
import cotato.growingpain.common.exception.AppException;
import cotato.growingpain.common.exception.ErrorCode;
Expand Down Expand Up @@ -49,15 +50,19 @@ public Token createLoginInfo(LoginRequest request) {
// 기존 회원이 존재하면 로그인 처리
Member member = existingMember.get();
if (!bCryptPasswordEncoder.matches(request.password(), member.getPassword())) {
throw new IllegalArgumentException("이메일 또는 비밀번호가 올바르지 않습니다.");
throw new AppException(ErrorCode.INVALID_PASSWORD);
}

if (member.getMemberRole() == MemberRole.PENDING) {
return jwtTokenProvider.createToken(member.getId(), request.email(), MemberRole.PENDING.getDescription());
}
String role = (member.getMemberRole() == MemberRole.PENDING)
? MemberRole.PENDING.getDescription()
: MemberRole.MEMBER.getDescription();

Token token = jwtTokenProvider.createToken(member.getId(), member.getEmail(), role);

// 토큰 생성 및 반환
return jwtTokenProvider.createToken(member.getId(), request.email(), MemberRole.MEMBER.getDescription());
// RefreshTokenEntity 저장 또는 업데이트
saveOrUpdateRefreshToken(member.getEmail(), token.getRefreshToken());

return token;
}
else {
// 신규 회원일 경우 회원가입 처리
Expand All @@ -66,15 +71,19 @@ public Token createLoginInfo(LoginRequest request) {

log.info("[회원 가입 서비스]: {}", request.email());

Member newMember = Member.builder()
Member member = Member.builder()
.password(bCryptPasswordEncoder.encode(request.password()))
.email(request.email())
.memberRole(MemberRole.PENDING)
.build();
memberRepository.save(newMember);
memberRepository.save(member);

// 회원가입 성공 후 토큰 생성 및 반환
return jwtTokenProvider.createToken(newMember.getId(), request.email(),MemberRole.PENDING.getDescription());
Token token = jwtTokenProvider.createToken(member.getId(), member.getEmail(), MemberRole.PENDING.getDescription());

saveOrUpdateRefreshToken(member.getEmail(), token.getRefreshToken());

return token;
}
}

Expand All @@ -95,7 +104,12 @@ public Token completeSignup(CompleteSignupRequest request, String accessToken) {
member.updateRole(MemberRole.MEMBER);
memberRepository.save(member);

return jwtTokenProvider.createToken(member.getId(), member.getEmail(), MemberRole.MEMBER.getDescription());
Token token = jwtTokenProvider.createToken(member.getId(), member.getEmail(), MemberRole.MEMBER.getDescription());

// RefreshTokenEntity 저장
saveOrUpdateRefreshToken(member.getEmail(), token.getRefreshToken());

return token;
}
log.info("memberRole = {}", member.getMemberRole());
return null;
Expand Down Expand Up @@ -126,33 +140,44 @@ public ReissueResponse tokenReissue(ReissueRequest request) {
throw new AppException(ErrorCode.TOKEN_EXPIRED);
}

if (!request.equals(request)) {
if (!findToken.getRefreshToken().equals(request.refreshToken())) {
log.warn("[쿠키로 들어온 토큰과 DB의 토큰이 일치하지 않음.]");
throw new AppException(ErrorCode.REFRESH_TOKEN_NOT_EXIST);
}

Token token = jwtTokenProvider.createToken(memberId, email, role);
findToken.updateRefreshToken(token.getRefreshToken());
refreshTokenRepository.save(findToken);

log.info("재발급 된 액세스 토큰: {}", token.getAccessToken());
log.info("재발급 된 refresh 토큰: {}", token.getRefreshToken());

// RefreshTokenEntity 업데이트
saveOrUpdateRefreshToken(email, token.getRefreshToken());
return ReissueResponse.from(token.getAccessToken(),token.getRefreshToken());
}

private void saveOrUpdateRefreshToken(String email, String refreshToken) {
RefreshTokenEntity refreshTokenEntity = refreshTokenRepository.findById(email)
.orElse(RefreshTokenEntity.builder().email(email).build());

refreshTokenEntity.updateRefreshToken(refreshToken);
refreshTokenRepository.save(refreshTokenEntity);
}

@Transactional
public void logout(LogoutRequest request) {
String memberId = jwtTokenProvider.getEmail(request.refreshToken());
RefreshTokenEntity existRefreshToken = refreshTokenRepository.findById(memberId)
String email = jwtTokenProvider.getEmail(request.refreshToken());

RefreshTokenEntity existRefreshToken = refreshTokenRepository.findById(email)
.orElseThrow(() -> new AppException(ErrorCode.REFRESH_TOKEN_NOT_EXIST));

setBlackList(request.refreshToken());
log.info("[로그아웃 된 리프레시 토큰 블랙리스트 처리]");
refreshTokenRepository.delete(existRefreshToken);
log.info("삭제 요청된 refreshToken: {}", request.refreshToken());
}

@Transactional
public ChangePasswordResponse changePassword(ChangePasswordRequest request){
public ResetPasswordResponse resetPassword(ResetPasswordRequest request){

Member member = memberRepository.findByEmail(request.email())
.orElseThrow(() -> new AppException(ErrorCode.EMAIL_NOT_FOUND));
Expand All @@ -170,7 +195,29 @@ public ChangePasswordResponse changePassword(ChangePasswordRequest request){

log.info("비밀번호 업데이트 완료: {}", member.getEmail());

return new ChangePasswordResponse(tempPassword);
return new ResetPasswordResponse(tempPassword);
}

@Transactional
public void changePassword(ChangePasswordRequest request, Long memberId) {

Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new AppException(ErrorCode.MEMBER_NOT_FOUND));

// 기존 비밀번호 검증
if (!bCryptPasswordEncoder.matches(request.currentPassword(), member.getPassword())) {
throw new AppException(ErrorCode.INVALID_PASSWORD);
}

// 새 비밀번호 패턴 검증
validateService.checkPasswordPattern(request.newPassword());

// 새 비밀번호로 업데이트
String encodedNewPassword = bCryptPasswordEncoder.encode(request.newPassword());
member.updatePassword(encodedNewPassword);
memberRepository.save(member);

log.info("비밀번호 번경 완료: {}", memberId);
}

private String generateTemporaryPassword() {
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/cotato/growingpain/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
Expand Down Expand Up @@ -59,8 +58,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(request -> request
.requestMatchers(WHITE_LIST).permitAll()
.requestMatchers(HttpMethod.GET, REQUIRED_AUTHENTICATE).authenticated()
.requestMatchers(HttpMethod.GET).permitAll()
.requestMatchers(REQUIRED_AUTHENTICATE).hasAuthority("ROLE_MEMBER")
.anyRequest().authenticated()
);
return http.build();
Expand Down

0 comments on commit 62588c3

Please sign in to comment.