Merge remote-tracking branch 'origin/feat/dev_251201' into feat/dev_251201

# Conflicts:
#	src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngFileCheckerApiController.java
#	src/main/resources/application-local.yml
This commit is contained in:
DanielLee
2025-12-12 18:54:34 +09:00
20 changed files with 177 additions and 148 deletions

View File

@@ -30,8 +30,8 @@ public class CustomAuthenticationProvider implements AuthenticationProvider {
.findByUserId(username) .findByUserId(username)
.orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND)); .orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND));
// 삭제 상태 // 미사용 상태
if (member.getStatus().equals(StatusType.DELETED.getId())) { if (member.getStatus().equals(StatusType.INACTIVE.getId())) {
throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND); throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND);
} }
@@ -39,15 +39,12 @@ public class CustomAuthenticationProvider implements AuthenticationProvider {
if (!BCrypt.checkpw(rawPassword, member.getPassword())) { if (!BCrypt.checkpw(rawPassword, member.getPassword())) {
// 실패 카운트 저장 // 실패 카운트 저장
int cnt = member.getLoginFailCount() + 1; int cnt = member.getLoginFailCount() + 1;
if (cnt >= 5) {
member.setStatus(StatusType.INACTIVE.getId());
}
member.setLoginFailCount(cnt); member.setLoginFailCount(cnt);
membersRepository.save(member); membersRepository.save(member);
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH); throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
} }
// 패스워드 실패 횟수 체크 // 로그인 실패 체크
if (member.getLoginFailCount() >= 5) { if (member.getLoginFailCount() >= 5) {
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_EXCEEDED); throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_EXCEEDED);
} }

View File

@@ -15,6 +15,9 @@ import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
public class CommonCodeDto { public class CommonCodeDto {
@@ -132,6 +135,33 @@ public class CommonCodeDto {
} }
} }
@Schema(name = "SearchReq", description = "검색 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class SearchReq {
// 검색 조건
private String name;
// 페이징 파라미터
private int page = 0;
private int size = 20;
private String sort;
public Pageable toPageable() {
if (sort != null && !sort.isEmpty()) {
String[] sortParams = sort.split(",");
String property = sortParams[0];
Sort.Direction direction =
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
return PageRequest.of(page, size, Sort.by(direction, property));
}
return PageRequest.of(page, size);
}
}
@Getter @Getter
public static class Clazzes { public static class Clazzes {

View File

@@ -8,8 +8,8 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum StatusType implements EnumType { public enum StatusType implements EnumType {
ACTIVE("활성"), ACTIVE("활성"),
INACTIVE("비활성"), INACTIVE("미사용"),
DELETED("삭제"); PENDING("보류");
private final String desc; private final String desc;

View File

@@ -0,0 +1,18 @@
package com.kamco.cd.kamcoback.common.utils;
import java.util.regex.Pattern;
public class CommonStringUtils {
/**
* 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호
*
* @param password
* @return
*/
public static boolean isValidPassword(String password) {
String passwordPattern =
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$";
return Pattern.matches(passwordPattern, password);
}
}

View File

@@ -472,23 +472,38 @@ public class GlobalExceptionHandler {
} }
@ExceptionHandler(CustomApiException.class) @ExceptionHandler(CustomApiException.class)
public ResponseEntity<ApiResponseDto<String>> handleCustomApiException( public ApiResponseDto<String> handleCustomApiException(
CustomApiException e, HttpServletRequest request) { CustomApiException e, HttpServletRequest request) {
log.warn("[CustomApiException] resource : {}", e.getMessage()); log.warn("[CustomApiException] resource : {}", e.getMessage());
String codeName = e.getCodeName(); String codeName = e.getCodeName();
HttpStatus status = e.getStatus(); HttpStatus status = e.getStatus();
String message = e.getMessage() == null ? ApiResponseCode.getMessage(codeName) : e.getMessage(); // String message = e.getMessage() == null ? ApiResponseCode.getMessage(codeName) :
// e.getMessage();
ApiResponseCode apiCode = ApiResponseCode.getCode(codeName); //
// ApiResponseCode apiCode = ApiResponseCode.getCode(codeName);
//
// ErrorLogEntity errorLog =
// saveErrorLogData(
// request, apiCode, status, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace());
//
// ApiResponseDto<String> body =
// ApiResponseDto.createException(apiCode, message, status, errorLog.getId());
ErrorLogEntity errorLog = ErrorLogEntity errorLog =
saveErrorLogData( saveErrorLogData(
request, apiCode, status, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); request,
ApiResponseCode.getCode(codeName),
HttpStatus.valueOf(status.value()),
ErrorLogDto.LogErrorLevel.WARNING,
e.getStackTrace());
ApiResponseDto<String> body = return ApiResponseDto.createException(
ApiResponseDto.createException(apiCode, message, status, errorLog.getId()); ApiResponseCode.getCode(codeName),
ApiResponseCode.getMessage(codeName),
HttpStatus.valueOf(status.value()),
errorLog.getId());
return new ResponseEntity<>(body, status); // return new ResponseEntity<>(body, status);
} }
} }

View File

@@ -62,6 +62,8 @@ public class SecurityConfig {
// ADMIN, REVIEWER 접근 // ADMIN, REVIEWER 접근
.requestMatchers("/api/test/review") .requestMatchers("/api/test/review")
.hasAnyRole("ADMIN", "REVIEWER") .hasAnyRole("ADMIN", "REVIEWER")
.requestMatchers("/error")
.permitAll()
.requestMatchers(HttpMethod.OPTIONS, "/**") .requestMatchers(HttpMethod.OPTIONS, "/**")
.permitAll() // preflight 허용 .permitAll() // preflight 허용
.requestMatchers( .requestMatchers(

View File

@@ -14,14 +14,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@@ -75,12 +74,9 @@ public class MapSheetMngFileCheckerApiController {
@Operation(summary = "파일 업로드", description = "파일 업로드 및 TIF 검증") @Operation(summary = "파일 업로드", description = "파일 업로드 및 TIF 검증")
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponseDto<String> uploadFile( public ApiResponseDto<String> uploadFile(
@RequestParam("file") MultipartFile file, @RequestPart("file") MultipartFile file,
@RequestParam("targetPath") String targetPath, @RequestParam("targetPath") String targetPath) {
@RequestParam(name = "overwrite", required = false, defaultValue = "true") return ApiResponseDto.createOK(mapSheetMngFileCheckerService.uploadFile(file, targetPath));
boolean overwrite) {
return ApiResponseDto.createOK(
mapSheetMngFileCheckerService.uploadFile(file, targetPath, overwrite));
} }
@Operation(summary = "파일 삭제", description = "중복 파일 등 파일 삭제") @Operation(summary = "파일 삭제", description = "중복 파일 등 파일 삭제")
@@ -130,8 +126,4 @@ public class MapSheetMngFileCheckerApiController {
*/ */
@PostMapping("/upload-test")
public String uploadTest(@RequestParam("name") String name) {
return "RECV:" + name;
}
} }

View File

@@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@@ -87,24 +86,4 @@ public class AdminApiController {
adminService.updateMembers(uuid, updateReq); adminService.updateMembers(uuid, updateReq);
return ApiResponseDto.createOK(UUID.randomUUID()); return ApiResponseDto.createOK(UUID.randomUUID());
} }
@Operation(summary = "관리자 계정 미사용 처리", description = "관리자 계정 미사용 처리")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "관리자 계정 미사용 처리",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = UUID.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@DeleteMapping("/delete/{uuid}")
public ApiResponseDto<UUID> deleteAccount(@PathVariable UUID uuid) {
adminService.deleteAccount(uuid);
return ApiResponseDto.createOK(uuid);
}
} }

View File

@@ -113,7 +113,7 @@ public class AuthController {
MembersDto.Member member = new MembersDto.Member(); MembersDto.Member member = new MembersDto.Member();
// 비활성 상태면 임시패스워드를 비교함 // 비활성 상태면 임시패스워드를 비교함
if (StatusType.INACTIVE.getId().equals(status)) { if (StatusType.PENDING.getId().equals(status)) {
if (!authService.isTempPasswordValid(request)) { if (!authService.isTempPasswordValid(request)) {
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH); throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
} }
@@ -124,8 +124,9 @@ public class AuthController {
request.getUsername(), request.getPassword())); request.getUsername(), request.getPassword()));
} }
// INACTIVE 비활성 상태(새로운 패스워드 입력 해야함), DELETED 탈퇴 // PENDING 비활성 상태(새로운 패스워드 입력 해야함)
if (!StatusType.ACTIVE.getId().equals(status)) { if (StatusType.PENDING.getId().equals(status)) {
member.setEmployeeNo(request.getUsername());
return ApiResponseDto.ok(new TokenResponse(status, null, null, member)); return ApiResponseDto.ok(new TokenResponse(status, null, null, member));
} }

View File

@@ -15,7 +15,6 @@ import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject; import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@@ -70,10 +69,6 @@ public class MembersApiController {
@PatchMapping("/{memberId}/password") @PatchMapping("/{memberId}/password")
public ApiResponseDto<String> resetPassword( public ApiResponseDto<String> resetPassword(
@PathVariable String memberId, @RequestBody @Valid MembersDto.InitReq initReq) { @PathVariable String memberId, @RequestBody @Valid MembersDto.InitReq initReq) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(memberId, initReq.getTempPassword()));
membersService.resetPassword(memberId, initReq); membersService.resetPassword(memberId, initReq);
return ApiResponseDto.createOK(memberId); return ApiResponseDto.createOK(memberId);
} }

View File

@@ -108,32 +108,26 @@ public class MembersDto {
@Schema(description = "관리자 유형", example = "ADMIN") @Schema(description = "관리자 유형", example = "ADMIN")
@NotBlank @NotBlank
@EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER만 가능합니다.") @EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER 만 가능합니다.")
private String userRole; private String userRole;
@Schema(description = "사번", example = "K20251212001")
@Size(max = 50)
private String employeeNo;
@Schema(description = "이름", example = "홍길동") @Schema(description = "이름", example = "홍길동")
@NotBlank @NotBlank
@Size(min = 2, max = 100) @Size(min = 2, max = 100)
private String name; private String name;
@Schema(description = "ID", example = "gildong")
@NotBlank
@Size(min = 2, max = 50)
private String userId;
@Schema(description = "임시 비밀번호", example = "q!w@e#r4") @Schema(description = "임시 비밀번호", example = "q!w@e#r4")
private String tempPassword; private String tempPassword;
@Schema(description = "사번", example = "123456") public AddReq(String userRole, String employeeNo, String name, String tempPassword) {
private String employeeNo;
public AddReq(
String userRole, String name, String userId, String tempPassword, String employeeNo) {
this.userRole = userRole; this.userRole = userRole;
this.name = name;
this.userId = userId;
this.tempPassword = tempPassword;
this.employeeNo = employeeNo; this.employeeNo = employeeNo;
this.name = name;
this.tempPassword = tempPassword;
} }
} }
@@ -141,10 +135,6 @@ public class MembersDto {
@Setter @Setter
public static class UpdateReq { public static class UpdateReq {
@Schema(description = "사번, 패스워드 변경시 필수 값", example = "11111")
@Size(max = 50)
private String employeeNo;
@Schema(description = "이름", example = "홍길동") @Schema(description = "이름", example = "홍길동")
@Size(min = 2, max = 100) @Size(min = 2, max = 100)
private String name; private String name;
@@ -157,8 +147,7 @@ public class MembersDto {
@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 employeeNo, String name, String tempPassword, String status) { public UpdateReq(String name, String tempPassword, String status) {
this.employeeNo = employeeNo;
this.name = name; this.name = name;
this.tempPassword = tempPassword; this.tempPassword = tempPassword;
this.status = status; this.status = status;

View File

@@ -11,6 +11,7 @@ public class MemberException {
public enum Field { public enum Field {
USER_ID, USER_ID,
EMPLOYEE_NO,
DEFAULT DEFAULT
} }

View File

@@ -1,9 +1,13 @@
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.utils.CommonStringUtils;
import com.kamco.cd.kamcoback.members.dto.MembersDto; import com.kamco.cd.kamcoback.members.dto.MembersDto;
import com.kamco.cd.kamcoback.postgres.core.MembersCoreService; import com.kamco.cd.kamcoback.postgres.core.MembersCoreService;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -22,6 +26,10 @@ public class AdminService {
*/ */
@Transactional @Transactional
public Long saveMember(MembersDto.AddReq addReq) { public Long saveMember(MembersDto.AddReq addReq) {
if (!CommonStringUtils.isValidPassword(addReq.getTempPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
}
return membersCoreService.saveMembers(addReq); return membersCoreService.saveMembers(addReq);
} }
@@ -33,16 +41,12 @@ 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.deleteMember(uuid);
} else {
// 수정
membersCoreService.updateMembers(uuid, updateReq); membersCoreService.updateMembers(uuid, updateReq);
} }
/**
* 관리자 계정 미사용 처리
*
* @param uuid
*/
@Transactional
public void deleteAccount(UUID uuid) {
membersCoreService.deleteAccount(uuid);
} }
} }

View File

@@ -1,10 +1,10 @@
package com.kamco.cd.kamcoback.members.service; package com.kamco.cd.kamcoback.members.service;
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.members.dto.MembersDto; import com.kamco.cd.kamcoback.members.dto.MembersDto;
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic; import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
import com.kamco.cd.kamcoback.postgres.core.MembersCoreService; import com.kamco.cd.kamcoback.postgres.core.MembersCoreService;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -37,21 +37,9 @@ public class MembersService {
@Transactional @Transactional
public void resetPassword(String id, MembersDto.InitReq initReq) { public void resetPassword(String id, MembersDto.InitReq initReq) {
if (!isValidPassword(initReq.getPassword())) { if (!CommonStringUtils.isValidPassword(initReq.getPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
} }
membersCoreService.resetPassword(id, initReq); membersCoreService.resetPassword(id, initReq);
} }
/**
* 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호
*
* @param password
* @return
*/
private boolean isValidPassword(String password) {
String passwordPattern =
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$";
return Pattern.matches(passwordPattern, password);
}
} }

View File

@@ -7,7 +7,7 @@ import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj; import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity;
import com.kamco.cd.kamcoback.postgres.repository.code.CommonCodeRepository; import com.kamco.cd.kamcoback.postgres.repository.code.CommonCodeRepository;
import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.SearchReq; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.SearchReq;
import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.EntityNotFoundException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;

View File

@@ -2,6 +2,9 @@ package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator; import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
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.common.utils.CommonStringUtils;
import com.kamco.cd.kamcoback.common.utils.UserUtil; import com.kamco.cd.kamcoback.common.utils.UserUtil;
import com.kamco.cd.kamcoback.members.dto.MembersDto; import com.kamco.cd.kamcoback.members.dto.MembersDto;
import com.kamco.cd.kamcoback.members.dto.MembersDto.AddReq; import com.kamco.cd.kamcoback.members.dto.MembersDto.AddReq;
@@ -18,6 +21,7 @@ import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mindrot.jbcrypt.BCrypt; import org.mindrot.jbcrypt.BCrypt;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@@ -34,8 +38,8 @@ public class MembersCoreService {
* @return * @return
*/ */
public Long saveMembers(AddReq addReq) { public Long saveMembers(AddReq addReq) {
if (membersRepository.existsByUserId(addReq.getUserId())) { if (membersRepository.existsByEmployeeNo(addReq.getEmployeeNo())) {
throw new DuplicateMemberException(Field.USER_ID, addReq.getUserId()); throw new DuplicateMemberException(Field.EMPLOYEE_NO, addReq.getEmployeeNo());
} }
// salt 생성, 사번이 salt // salt 생성, 사번이 salt
@@ -44,7 +48,7 @@ public class MembersCoreService {
String hashedPassword = BCrypt.hashpw(addReq.getTempPassword(), salt); String hashedPassword = BCrypt.hashpw(addReq.getTempPassword(), salt);
MemberEntity memberEntity = new MemberEntity(); MemberEntity memberEntity = new MemberEntity();
memberEntity.setUserId(addReq.getUserId()); memberEntity.setUserId(addReq.getEmployeeNo());
memberEntity.setUserRole(addReq.getUserRole()); memberEntity.setUserRole(addReq.getUserRole());
memberEntity.setTempPassword(addReq.getTempPassword().trim()); // 임시 패스워드는 암호화 하지 않음 memberEntity.setTempPassword(addReq.getTempPassword().trim()); // 임시 패스워드는 암호화 하지 않음
memberEntity.setPassword(hashedPassword); memberEntity.setPassword(hashedPassword);
@@ -71,41 +75,35 @@ public class MembersCoreService {
// 임시 패스워드는 암호화 하지 않음 // 임시 패스워드는 암호화 하지 않음
if (StringUtils.isNotBlank(updateReq.getTempPassword())) { if (StringUtils.isNotBlank(updateReq.getTempPassword())) {
// 임시 패스워드가 기존과 다르면 패스워드 변경으로 처리함 /**
// 상태 INACTIVE로 변경하여 사용자가 로그인할때 패스워드 변경하게함 * 임시 패스워드가 기존과 다르면 패스워드 변경으로 처리함 상태 PENDING 으로 변경하여 사용자가 로그인할때 패스워드 변경하게함 패스워드 리셋이므로 로그인
// 패스워드 리셋이므로 로그인 실패카운트 초기화처리함 * 실패카운트 초기화처리함
*/
if (!memberEntity.getTempPassword().equals(updateReq.getTempPassword().trim())) { if (!memberEntity.getTempPassword().equals(updateReq.getTempPassword().trim())) {
memberEntity.setStatus(StatusType.INACTIVE.getId()); // 패스워드 유효성 검사
if (!CommonStringUtils.isValidPassword(updateReq.getTempPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
}
memberEntity.setStatus(StatusType.PENDING.getId());
memberEntity.setLoginFailCount(0); memberEntity.setLoginFailCount(0);
} }
memberEntity.setTempPassword(updateReq.getTempPassword().trim()); memberEntity.setTempPassword(updateReq.getTempPassword().trim());
} }
if (StringUtils.isNotBlank(updateReq.getEmployeeNo())) {
memberEntity.setEmployeeNo(updateReq.getEmployeeNo());
}
if (StringUtils.isNotBlank(updateReq.getStatus())) {
memberEntity.setStatus(updateReq.getStatus());
}
memberEntity.setUpdtrUid(userUtil.getId()); memberEntity.setUpdtrUid(userUtil.getId());
membersRepository.save(memberEntity); membersRepository.save(memberEntity);
} }
/** /**
* 관리자 계정 삭제 처리 * 미사용 처리
* *
* @param uuid * @param uuid
*/ */
public void deleteAccount(UUID uuid) { public void deleteMember(UUID uuid) {
MemberEntity memberEntity = MemberEntity memberEntity =
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new);
memberEntity.setStatus(StatusType.INACTIVE.getId());
memberEntity.setStatus(StatusType.DELETED.getId());
memberEntity.setUpdatedDttm(ZonedDateTime.now());
memberEntity.setUpdtrUid(userUtil.getId());
membersRepository.save(memberEntity); membersRepository.save(memberEntity);
} }
@@ -116,7 +114,12 @@ public class MembersCoreService {
*/ */
public void resetPassword(String id, MembersDto.InitReq initReq) { public void resetPassword(String id, MembersDto.InitReq initReq) {
MemberEntity memberEntity = MemberEntity memberEntity =
membersRepository.findByUserId(id).orElseThrow(() -> new MemberNotFoundException()); membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException());
// 임시 패스워드 확인
if (!memberEntity.getTempPassword().equals(initReq.getTempPassword())) {
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
}
String salt = String salt =
BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim()); BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim());

View File

@@ -64,7 +64,7 @@ public class MemberEntity {
@Size(max = 20) @Size(max = 20)
@ColumnDefault("'INACTIVE'") @ColumnDefault("'INACTIVE'")
@Column(name = "status", length = 20) @Column(name = "status", length = 20)
private String status = StatusType.INACTIVE.getId(); private String status = StatusType.PENDING.getId();
@NotNull @NotNull
@ColumnDefault("now()") @ColumnDefault("now()")

View File

@@ -11,6 +11,10 @@ public interface MembersRepositoryCustom {
boolean existsByUserId(String userId); boolean existsByUserId(String userId);
boolean existsByEmployeeNo(String employeeNo);
Optional<MemberEntity> findByEmployeeNo(String employeeNo);
Optional<MemberEntity> findByUserId(String userId); Optional<MemberEntity> findByUserId(String userId);
Optional<MemberEntity> findByUUID(UUID uuid); Optional<MemberEntity> findByUUID(UUID uuid);

View File

@@ -40,6 +40,22 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
!= null; != null;
} }
/**
* 사용자 사번 조회
*
* @param employeeNo
* @return
*/
@Override
public boolean existsByEmployeeNo(String employeeNo) {
return queryFactory
.selectOne()
.from(memberEntity)
.where(memberEntity.employeeNo.eq(employeeNo))
.fetchFirst()
!= null;
}
/** /**
* 사용자 조회 user id * 사용자 조회 user id
* *
@@ -52,6 +68,21 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
queryFactory.selectFrom(memberEntity).where(memberEntity.userId.eq(userId)).fetchOne()); queryFactory.selectFrom(memberEntity).where(memberEntity.userId.eq(userId)).fetchOne());
} }
/**
* 사용자 조회 employeed no
*
* @param employeeNo
* @return
*/
@Override
public Optional<MemberEntity> findByEmployeeNo(String employeeNo) {
return Optional.ofNullable(
queryFactory
.selectFrom(memberEntity)
.where(memberEntity.employeeNo.eq(employeeNo))
.fetchOne());
}
/** /**
* 회원정보 목록 조회 * 회원정보 목록 조회
* *

View File

@@ -29,17 +29,6 @@ spring:
port: 6379 port: 6379
password: 1234 password: 1234
servlet:
multipart:
enabled: true
max-file-size: 1024MB
max-request-size: 2048MB
file-size-threshold: 10MB
server:
tomcat:
max-swallow-size: 2097152000 # 약 2GB
jwt: jwt:
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b" secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
access-token-validity-in-ms: 86400000 # 1일 access-token-validity-in-ms: 86400000 # 1일
@@ -49,13 +38,4 @@ token:
refresh-cookie-name: kamco-local # 개발용 쿠키 이름 refresh-cookie-name: kamco-local # 개발용 쿠키 이름
refresh-cookie-secure: false # 로컬 http 테스트면 false refresh-cookie-secure: false # 로컬 http 테스트면 false
logging:
level:
org:
springframework:
security: DEBUG
org.springframework.security: DEBUG
mapsheet:
upload:
skipGdalValidation: true