diff --git a/src/main/java/peer/backend/dto/privateinfo/enums/PrivateActions.java b/src/main/java/peer/backend/dto/privateinfo/enums/PrivateActions.java index c4f6819d..582925cf 100644 --- a/src/main/java/peer/backend/dto/privateinfo/enums/PrivateActions.java +++ b/src/main/java/peer/backend/dto/privateinfo/enums/PrivateActions.java @@ -1,9 +1,6 @@ package peer.backend.dto.privateinfo.enums; import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -import lombok.Setter; @Getter public enum PrivateActions { diff --git a/src/main/java/peer/backend/dto/profile/request/ChangePasswordRequest.java b/src/main/java/peer/backend/dto/profile/request/ChangePasswordRequest.java index bb5d1189..4f03b25b 100644 --- a/src/main/java/peer/backend/dto/profile/request/ChangePasswordRequest.java +++ b/src/main/java/peer/backend/dto/profile/request/ChangePasswordRequest.java @@ -3,15 +3,13 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import peer.backend.exception.IllegalArgumentException; @Getter @Builder @NoArgsConstructor -@AllArgsConstructor public class ChangePasswordRequest { @NotBlank(message = "비밀번호를 입력하세요.") @@ -22,4 +20,21 @@ public class ChangePasswordRequest { @NotBlank(message = "코드는 필수입니다.") private String code; + + public ChangePasswordRequest(String password, + String code) throws IllegalArgumentException{ + String errorMessage = ""; + if (password.isBlank()) + errorMessage = "비밀번호를 입력해야 합니다.\n"; + if (!password.isBlank() && (password.length() < 8 || password.length() > 20)) + errorMessage = "비밀번호는 반드시 8자 이상, 20자 이하여야 합니다.\n"; + if (!password.isBlank() && !password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*])[A-Za-z\\d!@#$%^&*]{8,20}$")) + errorMessage = "비밀번호는 대소문자, 숫자, 특수문자를 포함해야 합니다.\n"; + if (code.isBlank()) + errorMessage = "비정상적인 접근입니다."; + if (!errorMessage.isEmpty()) + throw new IllegalArgumentException(errorMessage); + this.password = password; + this.code = code; + } } diff --git a/src/main/java/peer/backend/dto/profile/request/PasswordRequest.java b/src/main/java/peer/backend/dto/profile/request/PasswordRequest.java index 34f980c7..6ef475af 100644 --- a/src/main/java/peer/backend/dto/profile/request/PasswordRequest.java +++ b/src/main/java/peer/backend/dto/profile/request/PasswordRequest.java @@ -1,9 +1,8 @@ package peer.backend.dto.profile.request; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import peer.backend.exception.IllegalArgumentException; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; @@ -12,7 +11,6 @@ @Getter @Builder @NoArgsConstructor -@AllArgsConstructor public class PasswordRequest { @NotBlank(message = "비밀번호를 입력하세요.") @@ -20,4 +18,17 @@ public class PasswordRequest { @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*])[A-Za-z\\d!@#$%^&*]{8,20}$", message = "대소문자, 숫자, 특수문자를 포함해야 합니다!") private String password; + + public PasswordRequest(String password) throws IllegalArgumentException { + String errorMessage = ""; + if (password.isBlank()) + errorMessage = "비밀번호를 입력해야 합니다.\n"; + if (!password.isBlank() && (password.length() < 8 || password.length() > 20)) + errorMessage = "비밀번호는 반드시 8자 이상, 20자 이하여야 합니다.\n"; + if (!password.isBlank() && !password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*])[A-Za-z\\d!@#$%^&*]{8,20}$")) + errorMessage = "비밀번호는 대소문자, 숫자, 특수문자를 포함해야 합니다.\n"; + if (!errorMessage.isEmpty()) + throw new IllegalArgumentException(errorMessage); + this.password = password; + } } diff --git a/src/main/java/peer/backend/dto/security/UserInfo.java b/src/main/java/peer/backend/dto/security/UserInfo.java index ddc42e1f..1d108e80 100644 --- a/src/main/java/peer/backend/dto/security/UserInfo.java +++ b/src/main/java/peer/backend/dto/security/UserInfo.java @@ -4,21 +4,15 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import peer.backend.entity.user.User; +import peer.backend.exception.IllegalArgumentException; @Getter @NoArgsConstructor -@AllArgsConstructor -@Builder public class UserInfo { - - // @NotBlank(message = "ID는 필수항목입니다.") -// private String userId; @NotBlank(message = "이메일은 필수항목입니다.") @Email(message = "이메일형식에 맞지 않습니다.") private String email; @@ -30,8 +24,8 @@ public class UserInfo { private String password; @NotBlank(message = "닉네임은 필수항목입니다.") - @Size(min = 2, max = 7, message = "2글자 이상 7글자 이하여야 합니다!") - @Pattern(regexp = "^[가-힣a-zA-Z0-9]{2,7}$", message = "한글, 대소문자, 숫자로만 이루어져야 합니다!") + @Size(min = 2, max = 30, message = "2글자 이상 30글자 이하여야 합니다!") + @Pattern(regexp = "^[가-힣a-zA-Z0-9]{2,30}$", message = "한글, 대소문자, 숫자로만 이루어져야 합니다!") private String nickname; @NotBlank(message = "이름은 필수항목입니다.") @@ -50,6 +44,54 @@ public class UserInfo { @Email(message = "이메일 형식에 맞지 않습니다.") private String socialEmail; + public UserInfo(String email, String password, String nickname, String name, String socialEmail) throws IllegalArgumentException { + String errorMessage = ""; + if (email.isBlank()) { + errorMessage += "이메일은 필수 항목입니다.\n"; + } + if(!email.isBlank() && !email.matches("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}")) { + errorMessage += "이메일형식에 맞지 않습니다.\n"; + } + if (password.isBlank()){ + errorMessage += "비밀번호는 필수항목입니다.\n"; + } + if (!password.isBlank() && (password.length() < 8 || password.length() > 20)) { + errorMessage += "비밀번호는 반드시 8자 이상, 20자 이하여야 합니다.\n"; + } + if (!password.isBlank() && !password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*])[A-Za-z\\d!@#$%^&*]{8,20}$")) { + errorMessage += "대소문자, 숫자, 특수문자를 포함해야 합니다!\n"; + } + if (nickname.isBlank()) { + errorMessage += "닉네임은 필수항목입니다.\n"; + } + if (!nickname.isBlank() && (nickname.length() < 2 || nickname.length() > 30)) { + errorMessage += "닉네임은 2글자 이상 30글자 이하여야 합니다.\n"; + } + if(!nickname.isBlank() && !nickname.matches("^[가-힣a-zA-Z0-9]{2,30}$")) { + errorMessage +="한글, 대소문자, 숫자로만 이루어져야 합니다!\n"; + } + if (name.isBlank()) { + errorMessage += "이름은 필수항목입니다.\n"; + } + if (!name.isBlank() && (name.length() < 2 || name.length() > 4)) { + errorMessage += "이름은 2글자 이상 4글자 이하여야 합니다.\n"; + } + if (!name.isBlank() && !name.matches("^[가-힣]{2,4}$")) { + errorMessage += "이름은 한글로만 이루어져야 합니다!\n"; + } + if (socialEmail != null && !socialEmail.matches("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}")) { + errorMessage += "Social 이메일이 형식에 맞지 않습니다.\n"; + } + if (!errorMessage.isEmpty()) + throw new IllegalArgumentException(errorMessage); + + this.email = email; + this.password = password; + this.nickname = nickname; + this.name = name; + this.socialEmail = socialEmail; + } + public User convertUser() { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); return User.builder() diff --git a/src/main/java/peer/backend/service/PrivateInfoWrappingService.java b/src/main/java/peer/backend/service/PrivateInfoWrappingService.java index 4fd59055..b456b1c7 100644 --- a/src/main/java/peer/backend/service/PrivateInfoWrappingService.java +++ b/src/main/java/peer/backend/service/PrivateInfoWrappingService.java @@ -7,7 +7,6 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Service; import peer.backend.dto.privateinfo.InitSecretDTO; import peer.backend.dto.privateinfo.InitTokenDTO; @@ -21,14 +20,15 @@ import peer.backend.exception.BadRequestException; import peer.backend.exception.ConflictException; import peer.backend.exception.ForbiddenException; + +import peer.backend.exception.IllegalArgumentException; import peer.backend.service.profile.PersonalInfoService; import java.nio.charset.CharsetDecoder; import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Base64; + import java.util.HashMap; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -148,7 +148,6 @@ public MainSeedDTO parseInitToken(InitTokenDTO jwt) { for (PrivateActions act : PrivateActions.values()) { if (act.getCode() == apiType) { result = this.makeTokenAndKey(act); - System.out.println("성공적으로 조건을 발견하였습니다. : " + act.getDescription()); success = true; } } @@ -172,42 +171,112 @@ private Claims parseSecretData(PrivateDataDTO data) { .getBody(); } - private UserInfo getDataForSignUP(PrivateDataDTO data) { + private UserInfo getDataForSignUP(PrivateDataDTO data) throws IllegalArgumentException { Claims target = this.parseSecretData(data); - + String email = target.get("email", String.class); String password = target.get("password", String.class);; String nickname = target.get("nickname", String.class);; String name = target.get("name", String.class);; String socialEmail = target.get("socialEmail", String.class);; - - - return UserInfo.builder() - .email(email) - .password(password) - .nickname(nickname) - .name(name) - .socialEmail(socialEmail) - .build(); + return new UserInfo(email, password, nickname, name, socialEmail); } - private PasswordRequest getDataForPasswordCheck(PrivateDataDTO data) { + private PasswordRequest getDataForPasswordCheck(PrivateDataDTO data) throws IllegalArgumentException { Claims target = this.parseSecretData(data); String password = target.get("password", String.class); - return PasswordRequest.builder() - .password(password) - .build(); + return new PasswordRequest(password); } - private ChangePasswordRequest getDataForPasswordChange(PrivateDataDTO data) { + private ChangePasswordRequest getDataForPasswordChange(PrivateDataDTO data) throws IllegalArgumentException { Claims target = this.parseSecretData(data); String password = target.get("password", String.class); String code = target.get("code", String.class); - return ChangePasswordRequest.builder() - .password(password) - .code(code) + return new ChangePasswordRequest(password, code); + } + + private MainSeedDTO makeTokenAndKey(PrivateActions type) { + SecureRandom randomMaker = new SecureRandom(type.getDescription().getBytes()); + + // 256바이트 난수 생성을 위한 byte배열 + byte[] values = new byte[256]; + randomMaker.nextBytes(values); + + // 16진수 문자열로 변환 + StringBuilder sb = new StringBuilder(); + for(byte b : values) { + sb.append(String.format("%02x", b)); + } + + // code 만들기 + Long result = randomMaker.nextLong() & Long.MAX_VALUE; + while(!this.checkCodeUniqueOrNotForToken(result.toString())) { + result = randomMaker.nextLong() & Long.MAX_VALUE; + } + + // code, act 기억 + this.saveCodeAndActionToRedis(result, type); + + MainSeedDTO data = MainSeedDTO.builder() + .seed(sb.toString()) + .code(result) .build(); + this.saveMainSeedToRedis(data); + + return data; + } + + public ResponseEntity processDataFromToken (User user, PrivateDataDTO data) { + Integer type = Integer.parseInt(Objects.requireNonNull(this.redisTemplateForSecret.opsForValue().get("act-" + data.getCode()))); + this.redisTemplateForSecret.delete("act-" + data.getCode()); + + if (type == PrivateActions.SIGNUP.getCode()){ + // 회원가입 폼 제출 로직 + UserInfo newUser; + try { + newUser = this.getDataForSignUP(data); } + catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + this.memberService.signUp(newUser); + return ResponseEntity.ok().build(); + + } else if (type == PrivateActions.PASSWORDCHECK.getCode()) { + // 비밀번호 확인 로직 + PasswordRequest request; + try { + request = this.getDataForPasswordCheck(data); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + if (!this.memberService.verificationPassword(request.getPassword(), user.getPassword())){ + throw new ForbiddenException("비밀번호가 일치하지 않습니다!"); + } + String uuid = this.personalInfoService.getChangePasswordCode(user.getId()); + HashMap body = new HashMap<>(); + body.put("code", uuid); + return ResponseEntity.status(HttpStatus.CREATED).body(body); + + } else if (type == PrivateActions.PASSWORDMODIFY.getCode()) { + // 비밀번호 변경 로직 + ChangePasswordRequest request; + try { + request = this.getDataForPasswordChange(data); } + catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + if (!this.personalInfoService.checkChangePasswordCode(user.getId(), request.getCode())) { + throw new ForbiddenException("유효하지 않은 코드입니다!"); + } + if (this.memberService.verificationPassword(request.getPassword(), user.getPassword())) { + throw new ConflictException("현재 비밀번호와 일치합니다!"); + } + this.personalInfoService.changePassword(user, request.getPassword()); + return ResponseEntity.status(HttpStatus.OK).build(); + } else { + throw new BadRequestException("비 정상적인 접근입니다."); + } } private MainSeedDTO makeTokenAndKey(PrivateActions type) {