관리자 관리 수정

This commit is contained in:
2025-12-15 10:38:26 +09:00
parent dfaa55ee49
commit 9baef15d6a
10 changed files with 75 additions and 84 deletions

View File

@@ -32,7 +32,7 @@ public class CustomAuthenticationProvider implements AuthenticationProvider {
// 미사용 상태 // 미사용 상태
if (member.getStatus().equals(StatusType.INACTIVE.getId())) { if (member.getStatus().equals(StatusType.INACTIVE.getId())) {
throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND); throw new CustomApiException(AuthErrorCode.INACTIVE_ID);
} }
// jBCrypt + 커스텀 salt 로 저장된 패스워드 비교 // jBCrypt + 커스텀 salt 로 저장된 패스워드 비교

View File

@@ -10,7 +10,9 @@ public enum AuthErrorCode implements ErrorCode {
LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED), LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED),
LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED); LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED),
INACTIVE_ID("INACTIVE_ID", HttpStatus.UNAUTHORIZED);
private final String code; private final String code;
private final HttpStatus status; private final HttpStatus status;

View File

@@ -1,13 +1,15 @@
package com.kamco.cd.kamcoback.common.utils; package com.kamco.cd.kamcoback.common.utils;
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.mindrot.jbcrypt.BCrypt;
public class CommonStringUtils { public class CommonStringUtils {
/** /**
* 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호 * 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호
* *
* @param password * @param password 벨리데이션 필요한 패스워드
* @return * @return
*/ */
public static boolean isValidPassword(String password) { public static boolean isValidPassword(String password) {
@@ -15,4 +17,16 @@ public class CommonStringUtils {
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$"; "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$";
return Pattern.matches(passwordPattern, password); return Pattern.matches(passwordPattern, password);
} }
/**
* 패스워드 암호화
*
* @param password 암호화 필요한 패스워드
* @param employeeNo salt 생성에 필요한 사원번호
* @return
*/
public static String hashPassword(String password, String employeeNo) {
String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(employeeNo.trim());
return BCrypt.hashpw(password.trim(), salt);
}
} }

View File

@@ -167,6 +167,7 @@ public class ApiResponseDto<T> {
LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."), LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."),
LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."), LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."),
LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."), LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."),
INACTIVE_ID("미사용 아이디 입니다."),
INVALID_EMAIL_TOKEN( INVALID_EMAIL_TOKEN(
"You can only reset your password within 24 hours from when the email was sent.\n" "You can only reset your password within 24 hours from when the email was sent.\n"
+ "To reset your password again, please submit a new request through \"Forgot" + "To reset your password again, please submit a new request through \"Forgot"

View File

@@ -4,8 +4,6 @@ import com.kamco.cd.kamcoback.auth.CustomUserDetails;
import com.kamco.cd.kamcoback.auth.JwtTokenProvider; import com.kamco.cd.kamcoback.auth.JwtTokenProvider;
import com.kamco.cd.kamcoback.auth.RefreshTokenService; import com.kamco.cd.kamcoback.auth.RefreshTokenService;
import com.kamco.cd.kamcoback.common.enums.StatusType; import com.kamco.cd.kamcoback.common.enums.StatusType;
import com.kamco.cd.kamcoback.common.enums.error.AuthErrorCode;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.members.dto.MembersDto; import com.kamco.cd.kamcoback.members.dto.MembersDto;
import com.kamco.cd.kamcoback.members.dto.SignInRequest; import com.kamco.cd.kamcoback.members.dto.SignInRequest;
@@ -112,17 +110,9 @@ public class AuthController {
Authentication authentication = null; Authentication authentication = null;
MembersDto.Member member = new MembersDto.Member(); MembersDto.Member member = new MembersDto.Member();
// 비활성 상태면 임시패스워드를 비교함 authentication =
if (StatusType.PENDING.getId().equals(status)) { authenticationManager.authenticate(
if (!authService.isTempPasswordValid(request)) { new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
}
} else {
authentication =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(), request.getPassword()));
}
// PENDING 비활성 상태(새로운 패스워드 입력 해야함) // PENDING 비활성 상태(새로운 패스워드 입력 해야함)
if (StatusType.PENDING.getId().equals(status)) { if (StatusType.PENDING.getId().equals(status)) {

View File

@@ -28,9 +28,7 @@ public class MembersDto {
private String userRole; private String userRole;
private String userRoleName; private String userRoleName;
private String name; private String name;
private String userId;
private String employeeNo; private String employeeNo;
private String tempPassword;
private String status; private String status;
private String statusName; private String statusName;
@JsonFormatDttm private ZonedDateTime createdDttm; @JsonFormatDttm private ZonedDateTime createdDttm;
@@ -43,9 +41,7 @@ public class MembersDto {
UUID uuid, UUID uuid,
String userRole, String userRole,
String name, String name,
String userId,
String employeeNo, String employeeNo,
String tempPassword,
String status, String status,
ZonedDateTime createdDttm, ZonedDateTime createdDttm,
ZonedDateTime updatedDttm, ZonedDateTime updatedDttm,
@@ -56,9 +52,7 @@ public class MembersDto {
this.userRole = userRole; this.userRole = userRole;
this.userRoleName = getUserRoleName(userRole); this.userRoleName = getUserRoleName(userRole);
this.name = name; this.name = name;
this.userId = userId;
this.employeeNo = employeeNo; this.employeeNo = employeeNo;
this.tempPassword = tempPassword;
this.status = status; this.status = status;
this.statusName = getStatusName(status); this.statusName = getStatusName(status);
this.createdDttm = createdDttm; this.createdDttm = createdDttm;
@@ -120,14 +114,16 @@ public class MembersDto {
@Size(min = 2, max = 100) @Size(min = 2, max = 100)
private String name; private String name;
@Schema(description = "임시 비밀번호", example = "q!w@e#r4") @NotBlank
private String tempPassword; @Schema(description = "패스워드", example = "")
@Size(max = 255)
private String password;
public AddReq(String userRole, String employeeNo, String name, String tempPassword) { public AddReq(String userRole, String employeeNo, String name, String password) {
this.userRole = userRole; this.userRole = userRole;
this.employeeNo = employeeNo; this.employeeNo = employeeNo;
this.name = name; this.name = name;
this.tempPassword = tempPassword; this.password = password;
} }
} }
@@ -139,18 +135,18 @@ public class MembersDto {
@Size(min = 2, max = 100) @Size(min = 2, max = 100)
private String name; private String name;
@Schema(description = "패스워드", example = "")
@Size(max = 255)
private String tempPassword;
@Schema(description = "상태", example = "ACTIVE") @Schema(description = "상태", example = "ACTIVE")
@EnumValid(enumClass = StatusType.class, message = "status는 ACTIVE, INACTIVE, DELETED 만 가능합니다.") @EnumValid(enumClass = StatusType.class, message = "status는 ACTIVE, INACTIVE, DELETED 만 가능합니다.")
private String status; private String status;
public UpdateReq(String name, String tempPassword, String status) { @Schema(description = "패스워드", example = "")
@Size(max = 255)
private String password;
public UpdateReq(String name, String status, String password) {
this.name = name; this.name = name;
this.tempPassword = tempPassword;
this.status = status; this.status = status;
this.password = password;
} }
} }
@@ -158,14 +154,15 @@ public class MembersDto {
@Setter @Setter
public static class InitReq { public static class InitReq {
@Schema(description = "변경 패스워드", example = "") @Schema(description = "기존 패스워드", example = "")
@Size(max = 255) @Size(max = 255)
@NotBlank @NotBlank
private String password; private String oldPassword;
@Schema(description = "초기 패스워드", example = "") @Schema(description = "신규 패스워드", example = "")
@Size(max = 255)
@NotBlank @NotBlank
private String tempPassword; private String newPassword;
} }
@Getter @Getter

View File

@@ -1,6 +1,5 @@
package com.kamco.cd.kamcoback.members.service; package com.kamco.cd.kamcoback.members.service;
import com.kamco.cd.kamcoback.common.enums.StatusType;
import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.utils.CommonStringUtils; import com.kamco.cd.kamcoback.common.utils.CommonStringUtils;
import com.kamco.cd.kamcoback.members.dto.MembersDto; import com.kamco.cd.kamcoback.members.dto.MembersDto;
@@ -26,7 +25,7 @@ public class AdminService {
*/ */
@Transactional @Transactional
public Long saveMember(MembersDto.AddReq addReq) { public Long saveMember(MembersDto.AddReq addReq) {
if (!CommonStringUtils.isValidPassword(addReq.getTempPassword())) { if (!CommonStringUtils.isValidPassword(addReq.getPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
} }
@@ -41,12 +40,6 @@ public class AdminService {
*/ */
@Transactional @Transactional
public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) { public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) {
if (StatusType.INACTIVE.getId().equals(updateReq.getStatus())) { membersCoreService.updateMembers(uuid, updateReq);
// 미사용 처리
membersCoreService.deleteMember(uuid);
} else {
// 수정
membersCoreService.updateMembers(uuid, updateReq);
}
} }
} }

View File

@@ -37,7 +37,7 @@ public class MembersService {
@Transactional @Transactional
public void resetPassword(String id, MembersDto.InitReq initReq) { public void resetPassword(String id, MembersDto.InitReq initReq) {
if (!CommonStringUtils.isValidPassword(initReq.getPassword())) { if (!CommonStringUtils.isValidPassword(initReq.getNewPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
} }
membersCoreService.resetPassword(id, initReq); membersCoreService.resetPassword(id, initReq);

View File

@@ -45,16 +45,16 @@ public class MembersCoreService {
// salt 생성, 사번이 salt // salt 생성, 사번이 salt
String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getEmployeeNo().trim()); String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getEmployeeNo().trim());
// 패스워드 암호화, 초기 패스워드 고정 // 패스워드 암호화, 초기 패스워드 고정
String hashedPassword = BCrypt.hashpw(addReq.getTempPassword(), salt); String hashedPassword = BCrypt.hashpw(addReq.getPassword(), salt);
MemberEntity memberEntity = new MemberEntity(); MemberEntity memberEntity = new MemberEntity();
memberEntity.setUserId(addReq.getEmployeeNo()); memberEntity.setUserId(addReq.getEmployeeNo());
memberEntity.setUserRole(addReq.getUserRole()); memberEntity.setUserRole(addReq.getUserRole());
memberEntity.setTempPassword(addReq.getTempPassword().trim()); // 임시 패스워드는 암호화 하지 않음
memberEntity.setPassword(hashedPassword); memberEntity.setPassword(hashedPassword);
memberEntity.setName(addReq.getName()); memberEntity.setName(addReq.getName());
memberEntity.setEmployeeNo(addReq.getEmployeeNo()); memberEntity.setEmployeeNo(addReq.getEmployeeNo());
memberEntity.setRgstrUidl(userUtil.getId()); memberEntity.setRgstrUidl(userUtil.getId());
memberEntity.setStatus(StatusType.PENDING.getId());
return membersRepository.save(memberEntity).getId(); return membersRepository.save(memberEntity).getId();
} }
@@ -73,37 +73,25 @@ public class MembersCoreService {
memberEntity.setName(updateReq.getName()); memberEntity.setName(updateReq.getName());
} }
// 임시 패스워드는 암호화 하지 않음 if (StringUtils.isNotBlank(updateReq.getStatus())) {
if (StringUtils.isNotBlank(updateReq.getTempPassword())) { memberEntity.setStatus(updateReq.getStatus());
/** }
* 임시 패스워드가 기존과 다르면 패스워드 변경으로 처리함 상태 PENDING 으로 변경하여 사용자가 로그인할때 패스워드 변경하게함 패스워드 리셋이므로 로그인
* 실패카운트 초기화처리함
*/
if (!memberEntity.getTempPassword().equals(updateReq.getTempPassword().trim())) {
// 패스워드 유효성 검사
if (!CommonStringUtils.isValidPassword(updateReq.getTempPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
}
memberEntity.setStatus(StatusType.PENDING.getId()); if (StringUtils.isNotBlank(updateReq.getPassword())) {
memberEntity.setLoginFailCount(0);
// 패스워드 유효성 검사
if (!CommonStringUtils.isValidPassword(updateReq.getPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
} }
memberEntity.setTempPassword(updateReq.getTempPassword().trim());
String password =
CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getPassword());
memberEntity.setStatus(StatusType.PENDING.getId());
memberEntity.setLoginFailCount(0);
memberEntity.setPassword(password);
} }
memberEntity.setUpdtrUid(userUtil.getId()); memberEntity.setUpdtrUid(userUtil.getId());
membersRepository.save(memberEntity);
}
/**
* 미사용 처리
*
* @param uuid
*/
public void deleteMember(UUID uuid) {
MemberEntity memberEntity =
membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new);
memberEntity.setStatus(StatusType.INACTIVE.getId());
membersRepository.save(memberEntity); membersRepository.save(memberEntity);
} }
@@ -116,18 +104,16 @@ public class MembersCoreService {
MemberEntity memberEntity = MemberEntity memberEntity =
membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException()); membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException());
// 임시 패스워드 확인 // 기존 패스워드 확인
if (!memberEntity.getTempPassword().equals(initReq.getTempPassword())) { if (!BCrypt.checkpw(initReq.getOldPassword(), memberEntity.getPassword())) {
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH); throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
} }
String salt = String password =
BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim()); CommonStringUtils.hashPassword(initReq.getOldPassword(), memberEntity.getPassword());
// 패스워드 암호화
String hashedPassword = BCrypt.hashpw(initReq.getPassword(), salt);
memberEntity.setPassword(hashedPassword); memberEntity.setPassword(password);
memberEntity.setStatus("ACTIVE"); memberEntity.setStatus(StatusType.ACTIVE.getId());
memberEntity.setUpdatedDttm(ZonedDateTime.now()); memberEntity.setUpdatedDttm(ZonedDateTime.now());
memberEntity.setUpdtrUid(memberEntity.getId()); memberEntity.setUpdtrUid(memberEntity.getId());
membersRepository.save(memberEntity); membersRepository.save(memberEntity);

View File

@@ -9,11 +9,13 @@ import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.sql.Types;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.JdbcTypeCode;
@Getter @Getter
@Setter @Setter
@@ -62,7 +64,6 @@ public class MemberEntity {
private String password; private String password;
@Size(max = 20) @Size(max = 20)
@ColumnDefault("'INACTIVE'")
@Column(name = "status", length = 20) @Column(name = "status", length = 20)
private String status = StatusType.PENDING.getId(); private String status = StatusType.PENDING.getId();
@@ -90,4 +91,11 @@ public class MemberEntity {
@Column(name = "updtr_uid") @Column(name = "updtr_uid")
private Long updtrUid; private Long updtrUid;
@Column(name = "status_chg_dttm")
private ZonedDateTime statusChgDttm;
@JdbcTypeCode(Types.CHAR)
@Column(name = "pwd_reset_yn", columnDefinition = "CHAR(1)")
private String pwdResetYn;
} }