Merge remote-tracking branch 'origin/feat/dev_251201' into feat/dev_251201
This commit is contained in:
@@ -1,31 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.auth;
|
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.members.service.AuthService;
|
|
||||||
import java.util.UUID;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AuthFailureEventListener implements ApplicationListener<AbstractAuthenticationFailureEvent> {
|
|
||||||
|
|
||||||
private final AuthService authService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplicationEvent(AbstractAuthenticationFailureEvent event) {
|
|
||||||
// 로그인 시도에 사용된 (username)
|
|
||||||
Object principal = event.getAuthentication().getPrincipal();
|
|
||||||
|
|
||||||
if (principal instanceof String username) {
|
|
||||||
// 로그인 실패 카운트 증가 로직 호출
|
|
||||||
authService.loginFail(UUID.fromString(username));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsAsyncExecution() {
|
|
||||||
return ApplicationListener.super.supportsAsyncExecution();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.kamco.cd.kamcoback.auth;
|
package com.kamco.cd.kamcoback.auth;
|
||||||
|
|
||||||
|
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.postgres.entity.MemberEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||||
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
|
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.mindrot.jbcrypt.BCrypt;
|
import org.mindrot.jbcrypt.BCrypt;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
@@ -24,18 +26,35 @@ public class CustomAuthenticationProvider implements AuthenticationProvider {
|
|||||||
String username = authentication.getName();
|
String username = authentication.getName();
|
||||||
String rawPassword = authentication.getCredentials().toString();
|
String rawPassword = authentication.getCredentials().toString();
|
||||||
|
|
||||||
// 1. 유저 조회
|
// 유저 조회
|
||||||
MemberEntity member =
|
MemberEntity member =
|
||||||
membersRepository
|
membersRepository
|
||||||
.findByUserId(username)
|
.findByUserId(username)
|
||||||
.orElseThrow(() -> new BadCredentialsException("ID 또는 비밀번호가 일치하지 않습니다."));
|
.orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND));
|
||||||
|
|
||||||
// 2. jBCrypt + 커스텀 salt 로 저장된 패스워드 비교
|
// jBCrypt + 커스텀 salt 로 저장된 패스워드 비교
|
||||||
if (!BCrypt.checkpw(rawPassword, member.getPassword())) {
|
if (!BCrypt.checkpw(rawPassword, member.getPassword())) {
|
||||||
throw new BadCredentialsException("ID 또는 비밀번호가 일치하지 않습니다.");
|
// 실패 카운트 저장
|
||||||
|
int cnt = member.getLoginFailCount() + 1;
|
||||||
|
if (cnt >= 5) {
|
||||||
|
member.setStatus(StatusType.INACTIVE.getId());
|
||||||
|
}
|
||||||
|
member.setLoginFailCount(cnt);
|
||||||
|
membersRepository.save(member);
|
||||||
|
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 인증 성공 → UserDetails 생성
|
// 삭제 상태
|
||||||
|
if (member.getStatus().equals(StatusType.DELETED.getId())) {
|
||||||
|
throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 패스워드 실패 횟수 체크
|
||||||
|
if (member.getLoginFailCount() >= 5) {
|
||||||
|
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_EXCEEDED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 인증 성공 → UserDetails 생성
|
||||||
CustomUserDetails userDetails = new CustomUserDetails(member);
|
CustomUserDetails userDetails = new CustomUserDetails(member);
|
||||||
|
|
||||||
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ public class CommonCodeApiController {
|
|||||||
// .map(Clazzes::new)
|
// .map(Clazzes::new)
|
||||||
// .toList();
|
// .toList();
|
||||||
|
|
||||||
//변화탐지 clazz API : enum -> 공통코드로 변경
|
// 변화탐지 clazz API : enum -> 공통코드로 변경
|
||||||
List<CommonCodeDto.Clazzes> list =
|
List<CommonCodeDto.Clazzes> list =
|
||||||
commonCodeUtil.getChildCodesByParentCode("0000").stream()
|
commonCodeUtil.getChildCodesByParentCode("0000").stream()
|
||||||
.map(
|
.map(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import lombok.Getter;
|
|||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum RoleType implements EnumType {
|
public enum RoleType implements EnumType {
|
||||||
ROLE_ADMIN("시스템 관리자"),
|
ROLE_ADMIN("관리자"),
|
||||||
ROLE_LABELER("라벨러"),
|
ROLE_LABELER("라벨러"),
|
||||||
ROLE_REVIEWER("검수자");
|
ROLE_REVIEWER("검수자");
|
||||||
|
|
||||||
@@ -22,4 +22,13 @@ public enum RoleType implements EnumType {
|
|||||||
public String getText() {
|
public String getText() {
|
||||||
return desc;
|
return desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RoleType from(String value) {
|
||||||
|
for (RoleType type : values()) {
|
||||||
|
if (type.name().equalsIgnoreCase(value)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.kamco.cd.kamcoback.common.enums;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.config.enums.EnumType;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum StatusType implements EnumType {
|
||||||
|
ACTIVE("활성"),
|
||||||
|
INACTIVE("비활성"),
|
||||||
|
DELETED("삭제");
|
||||||
|
|
||||||
|
private final String desc;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return name();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.kamco.cd.kamcoback.common.enums.error;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.common.utils.ErrorCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum AuthErrorCode implements ErrorCode {
|
||||||
|
LOGIN_ID_NOT_FOUND("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED),
|
||||||
|
|
||||||
|
LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED),
|
||||||
|
|
||||||
|
LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED);
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final HttpStatus status;
|
||||||
|
|
||||||
|
AuthErrorCode(String code, HttpStatus status) {
|
||||||
|
this.code = code;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.kamco.cd.kamcoback.common.exception;
|
package com.kamco.cd.kamcoback.common.exception;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.common.utils.ErrorCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
@@ -19,4 +20,9 @@ public class CustomApiException extends RuntimeException {
|
|||||||
this.codeName = codeName;
|
this.codeName = codeName;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CustomApiException(ErrorCode errorCode) {
|
||||||
|
this.codeName = errorCode.getCode();
|
||||||
|
this.status = errorCode.getStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.kamco.cd.kamcoback.common.utils;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
public interface ErrorCode {
|
||||||
|
|
||||||
|
String getCode();
|
||||||
|
|
||||||
|
HttpStatus getStatus();
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.kamco.cd.kamcoback.common.utils;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.auth.CustomUserDetails;
|
||||||
|
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||||
|
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserUtil {
|
||||||
|
|
||||||
|
public MembersDto.Member getCurrentUser() {
|
||||||
|
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
|
||||||
|
.filter(auth -> auth.getPrincipal() instanceof CustomUserDetails)
|
||||||
|
.map(
|
||||||
|
auth -> {
|
||||||
|
CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();
|
||||||
|
MemberEntity m = user.getMember();
|
||||||
|
return new MembersDto.Member(m.getId(), m.getName(), m.getEmployeeNo());
|
||||||
|
})
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
MembersDto.Member user = getCurrentUser();
|
||||||
|
return user != null ? user.getId() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
MembersDto.Member user = getCurrentUser();
|
||||||
|
return user != null ? user.getName() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmployeeNo() {
|
||||||
|
MembersDto.Member user = getCurrentUser();
|
||||||
|
return user != null ? user.getEmployeeNo() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,6 @@ public class HtmlEscapeDeserializer extends JsonDeserializer<Object> {
|
|||||||
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
|
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
|
||||||
throws IOException, JacksonException {
|
throws IOException, JacksonException {
|
||||||
String value = jsonParser.getValueAsString();
|
String value = jsonParser.getValueAsString();
|
||||||
System.out.println("🔥 HtmlEscapeDeserializer 실행됨: " + value);
|
|
||||||
System.out.println("convert : " + (value == null ? null : HtmlUtils.htmlEscape(value)));
|
|
||||||
return value == null ? null : HtmlUtils.htmlEscape(value);
|
return value == null ? null : HtmlUtils.htmlEscape(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import java.util.List;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
@@ -28,33 +30,33 @@ public class SecurityConfig {
|
|||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
.csrf(csrf -> csrf.disable()) // CSRF 보안 기능 비활성화
|
.csrf(csrf -> csrf.disable()) // CSRF 보안 기능 비활성화
|
||||||
.sessionManagement(
|
.sessionManagement(
|
||||||
sm ->
|
sm ->
|
||||||
sm.sessionCreationPolicy(
|
sm.sessionCreationPolicy(
|
||||||
SessionCreationPolicy.STATELESS)) // 서버 세션 만들지 않음, 요청은 JWT 인증
|
SessionCreationPolicy.STATELESS)) // 서버 세션 만들지 않음, 요청은 JWT 인증
|
||||||
.formLogin(form -> form.disable()) // react에서 로그인 요청 관리
|
.formLogin(form -> form.disable()) // react에서 로그인 요청 관리
|
||||||
.httpBasic(basic -> basic.disable()) // 기본 basic 인증 비활성화 JWT 인증사용
|
.httpBasic(basic -> basic.disable()) // 기본 basic 인증 비활성화 JWT 인증사용
|
||||||
.logout(logout -> logout.disable()) // 기본 로그아웃 비활성화 JWT는 서버 상태가 없으므로 로그아웃 처리 필요 없음
|
.logout(logout -> logout.disable()) // 기본 로그아웃 비활성화 JWT는 서버 상태가 없으므로 로그아웃 처리 필요 없음
|
||||||
.authenticationProvider(
|
.authenticationProvider(
|
||||||
customAuthenticationProvider) // 로그인 패스워드 비교방식 스프링 기본 Provider 사용안함 커스텀 사용
|
customAuthenticationProvider) // 로그인 패스워드 비교방식 스프링 기본 Provider 사용안함 커스텀 사용
|
||||||
.authorizeHttpRequests(
|
.authorizeHttpRequests(
|
||||||
auth ->
|
auth ->
|
||||||
auth.anyRequest().permitAll());
|
auth.requestMatchers(HttpMethod.OPTIONS, "/**")
|
||||||
// requestMatchers(HttpMethod.OPTIONS, "/**")
|
.permitAll() // preflight 허용
|
||||||
// .permitAll() // preflight 허용
|
.requestMatchers(
|
||||||
// .requestMatchers(
|
"/api/auth/signin",
|
||||||
// "/api/auth/signin",
|
"/api/auth/refresh",
|
||||||
// "/api/auth/refresh",
|
"/swagger-ui/**",
|
||||||
// "/swagger-ui/**",
|
"/api/members/{memberId}/password",
|
||||||
// "/v3/api-docs/**")
|
"/v3/api-docs/**")
|
||||||
// .permitAll()
|
.permitAll()
|
||||||
// .anyRequest()
|
.anyRequest()
|
||||||
// .authenticated())
|
.authenticated())
|
||||||
// .addFilterBefore(
|
.addFilterBefore(
|
||||||
// jwtAuthenticationFilter,
|
jwtAuthenticationFilter,
|
||||||
// UsernamePasswordAuthenticationFilter
|
UsernamePasswordAuthenticationFilter
|
||||||
// .class) // 요청 들어오면 먼저 JWT 토큰 검사 후 security context 에 사용자 정보 저장.
|
.class) // 요청 들어오면 먼저 JWT 토큰 검사 후 security context 에 사용자 정보 저장.
|
||||||
;
|
;
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
@@ -62,7 +64,7 @@ public class SecurityConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration)
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
return configuration.getAuthenticationManager();
|
return configuration.getAuthenticationManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,9 @@ public class ApiResponseDto<T> {
|
|||||||
NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."),
|
NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."),
|
||||||
NOT_FOUND_USER("사용자를 찾을 수 없습니다."),
|
NOT_FOUND_USER("사용자를 찾을 수 없습니다."),
|
||||||
UNPROCESSABLE_ENTITY("이 데이터는 삭제할 수 없습니다."),
|
UNPROCESSABLE_ENTITY("이 데이터는 삭제할 수 없습니다."),
|
||||||
|
LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."),
|
||||||
|
LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."),
|
||||||
|
LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."),
|
||||||
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"
|
||||||
|
|||||||
@@ -5,4 +5,17 @@ public interface EnumType {
|
|||||||
String getId();
|
String getId();
|
||||||
|
|
||||||
String getText();
|
String getText();
|
||||||
|
|
||||||
|
// code로 text
|
||||||
|
static <E extends Enum<E> & EnumType> E fromId(Class<E> enumClass, String id) {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (E e : enumClass.getEnumConstants()) {
|
||||||
|
if (id.equalsIgnoreCase(e.getId())) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // 못 찾으면 null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@Tag(name = "추론관리 분석결과", description = "추론관리 분석결과")
|
@Tag(name = "추론관리 분석결과", description = "추론관리 분석결과")
|
||||||
@RequestMapping({"/demo/inf/res", "/api/inf/res"})
|
@RequestMapping({"/api/inf/res"})
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@RestController
|
@RestController
|
||||||
public class InferenceResultApiController {
|
public class InferenceResultApiController {
|
||||||
|
|||||||
@@ -30,78 +30,78 @@ public class AdminApiController {
|
|||||||
|
|
||||||
@Operation(summary = "관리자 계정 등록", description = "관리자 계정 등록")
|
@Operation(summary = "관리자 계정 등록", description = "관리자 계정 등록")
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
value = {
|
value = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "201",
|
responseCode = "201",
|
||||||
description = "등록 성공",
|
description = "등록 성공",
|
||||||
content =
|
content =
|
||||||
@Content(
|
@Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
schema = @Schema(implementation = Long.class))),
|
schema = @Schema(implementation = Long.class))),
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||||
})
|
})
|
||||||
@PostMapping("/join")
|
@PostMapping("/join")
|
||||||
public ApiResponseDto<Long> saveMember(
|
public ApiResponseDto<Long> saveMember(
|
||||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
description = "관리자 계정 등록",
|
description = "관리자 계정 등록",
|
||||||
required = true,
|
required = true,
|
||||||
content =
|
content =
|
||||||
@Content(
|
@Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
schema = @Schema(implementation = MembersDto.AddReq.class)))
|
schema = @Schema(implementation = MembersDto.AddReq.class)))
|
||||||
@RequestBody
|
@RequestBody
|
||||||
@Valid
|
@Valid
|
||||||
MembersDto.AddReq addReq) {
|
MembersDto.AddReq addReq) {
|
||||||
|
|
||||||
return ApiResponseDto.createOK(adminService.saveMember(addReq));
|
return ApiResponseDto.createOK(adminService.saveMember(addReq));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "관리자 계정 수정", description = "관리자 계정 수정")
|
@Operation(summary = "관리자 계정 수정", description = "관리자 계정 수정")
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
value = {
|
value = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "201",
|
responseCode = "201",
|
||||||
description = "수정 성공",
|
description = "수정 성공",
|
||||||
content =
|
content =
|
||||||
@Content(
|
@Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
schema = @Schema(implementation = Long.class))),
|
schema = @Schema(implementation = Long.class))),
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||||
})
|
})
|
||||||
@PutMapping("/{uuid}")
|
@PutMapping("/{uuid}")
|
||||||
public ApiResponseDto<UUID> updateMembers(
|
public ApiResponseDto<UUID> updateMembers(
|
||||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
description = "관리자 계정 수정",
|
description = "관리자 계정 수정",
|
||||||
required = true,
|
required = true,
|
||||||
content =
|
content =
|
||||||
@Content(
|
@Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
schema = @Schema(implementation = MembersDto.UpdateReq.class)))
|
schema = @Schema(implementation = MembersDto.UpdateReq.class)))
|
||||||
@PathVariable
|
@PathVariable
|
||||||
UUID uuid,
|
UUID uuid,
|
||||||
@RequestBody @Valid MembersDto.UpdateReq updateReq) {
|
@RequestBody @Valid MembersDto.UpdateReq updateReq) {
|
||||||
adminService.updateMembers(uuid, updateReq);
|
adminService.updateMembers(uuid, updateReq);
|
||||||
return ApiResponseDto.createOK(UUID.randomUUID());
|
return ApiResponseDto.createOK(UUID.randomUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "관리자 계정 미사용 처리", description = "관리자 계정 미사용 처리")
|
@Operation(summary = "관리자 계정 미사용 처리", description = "관리자 계정 미사용 처리")
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
value = {
|
value = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "201",
|
responseCode = "201",
|
||||||
description = "관리자 계정 미사용 처리",
|
description = "관리자 계정 미사용 처리",
|
||||||
content =
|
content =
|
||||||
@Content(
|
@Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
schema = @Schema(implementation = UUID.class))),
|
schema = @Schema(implementation = UUID.class))),
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||||
})
|
})
|
||||||
@DeleteMapping("/delete/{uuid}")
|
@DeleteMapping("/delete/{uuid}")
|
||||||
public ApiResponseDto<UUID> deleteAccount(@PathVariable UUID uuid) {
|
public ApiResponseDto<UUID> deleteAccount(@PathVariable UUID uuid) {
|
||||||
adminService.deleteAccount(uuid);
|
adminService.deleteAccount(uuid);
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package com.kamco.cd.kamcoback.members;
|
package com.kamco.cd.kamcoback.members;
|
||||||
|
|
||||||
|
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.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.SignInRequest;
|
import com.kamco.cd.kamcoback.members.dto.SignInRequest;
|
||||||
|
import com.kamco.cd.kamcoback.members.dto.TokenResponse;
|
||||||
import com.kamco.cd.kamcoback.members.service.AuthService;
|
import com.kamco.cd.kamcoback.members.service.AuthService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
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;
|
||||||
@@ -50,80 +54,118 @@ public class AuthController {
|
|||||||
@Operation(summary = "로그인", description = "사번으로 로그인하여 액세스/리프레시 토큰을 발급.")
|
@Operation(summary = "로그인", description = "사번으로 로그인하여 액세스/리프레시 토큰을 발급.")
|
||||||
@ApiResponses({
|
@ApiResponses({
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "200",
|
responseCode = "200",
|
||||||
description = "로그인 성공",
|
description = "로그인 성공",
|
||||||
content = @Content(schema = @Schema(implementation = TokenResponse.class))),
|
content = @Content(schema = @Schema(implementation = TokenResponse.class))),
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "401",
|
responseCode = "401",
|
||||||
description = "ID 또는 비밀번호 불일치",
|
description = "로그인 실패 (아이디/비밀번호 오류, 계정잠금 등)",
|
||||||
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
|
content =
|
||||||
@ApiResponse(
|
@Content(
|
||||||
responseCode = "400",
|
schema = @Schema(implementation = ErrorResponse.class),
|
||||||
description = "미사용 상태",
|
examples = {
|
||||||
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
@ExampleObject(
|
||||||
|
name = "아이디 입력 오류",
|
||||||
|
description = "존재하지 않는 아이디",
|
||||||
|
value =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"code": "LOGIN_ID_NOT_FOUND",
|
||||||
|
"message": "아이디를 잘못 입력하셨습니다."
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
@ExampleObject(
|
||||||
|
name = "비밀번호 입력 오류 (4회 이하)",
|
||||||
|
description = "아이디는 정상, 비밀번호를 여러 번 틀린 경우",
|
||||||
|
value =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"code": "LOGIN_PASSWORD_MISMATCH",
|
||||||
|
"message": "비밀번호를 잘못 입력하셨습니다."
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
@ExampleObject(
|
||||||
|
name = "비밀번호 오류 횟수 초과",
|
||||||
|
description = "비밀번호 5회 이상 오류로 계정 잠김",
|
||||||
|
value =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"code": "LOGIN_PASSWORD_EXCEEDED",
|
||||||
|
"message": "비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다. 로그인 오류에 대해 관리자에게 문의하시기 바랍니다."
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
public ApiResponseDto<TokenResponse> signin(
|
public ApiResponseDto<TokenResponse> signin(
|
||||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
description = "로그인 요청 정보",
|
description = "로그인 요청 정보",
|
||||||
required = true)
|
required = true)
|
||||||
@RequestBody
|
@RequestBody
|
||||||
SignInRequest request,
|
SignInRequest request,
|
||||||
HttpServletResponse response) {
|
HttpServletResponse response) {
|
||||||
|
|
||||||
Authentication authentication =
|
Authentication authentication =
|
||||||
authenticationManager.authenticate(
|
authenticationManager.authenticate(
|
||||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
|
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
|
||||||
|
|
||||||
String status = authService.getUserStatus(request);
|
String status = authService.getUserStatus(request);
|
||||||
if ("INACTIVE".equals(status)) {
|
|
||||||
return ApiResponseDto.ok(new TokenResponse(status, null, null));
|
MembersDto.Member member = new MembersDto.Member();
|
||||||
|
|
||||||
|
// INACTIVE 비활성 상태(새로운 패스워드 입력 해야함), DELETED 탈퇴
|
||||||
|
if (!"ACTIVE".equals(status)) {
|
||||||
|
return ApiResponseDto.ok(new TokenResponse(status, null, null, member));
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = authentication.getName(); // UserDetailsService 에서 사용한 username
|
String username = authentication.getName(); // UserDetailsService 에서 사용한 username
|
||||||
|
|
||||||
// 로그인 시간 저장
|
|
||||||
authService.saveLogin(UUID.fromString(username));
|
|
||||||
|
|
||||||
String accessToken = jwtTokenProvider.createAccessToken(username);
|
String accessToken = jwtTokenProvider.createAccessToken(username);
|
||||||
String refreshToken = jwtTokenProvider.createRefreshToken(username);
|
String refreshToken = jwtTokenProvider.createRefreshToken(username);
|
||||||
|
|
||||||
// Redis에 RefreshToken 저장 (TTL = 7일)
|
// Redis에 RefreshToken 저장 (TTL = 7일)
|
||||||
refreshTokenService.save(
|
refreshTokenService.save(
|
||||||
username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
|
username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
|
||||||
|
|
||||||
// HttpOnly + Secure 쿠키에 RefreshToken 저장
|
// HttpOnly + Secure 쿠키에 RefreshToken 저장
|
||||||
ResponseCookie cookie =
|
ResponseCookie cookie =
|
||||||
ResponseCookie.from(refreshCookieName, refreshToken)
|
ResponseCookie.from(refreshCookieName, refreshToken)
|
||||||
.httpOnly(true)
|
.httpOnly(true)
|
||||||
.secure(refreshCookieSecure)
|
.secure(refreshCookieSecure)
|
||||||
.path("/")
|
.path("/")
|
||||||
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
|
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
|
||||||
.sameSite("Strict")
|
.sameSite("Strict")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||||
|
|
||||||
return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken));
|
CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal();
|
||||||
|
member.setId(user.getMember().getId());
|
||||||
|
member.setName(user.getMember().getName());
|
||||||
|
member.setEmployeeNo(user.getMember().getEmployeeNo());
|
||||||
|
|
||||||
|
// 인증 성공 로그인 시간 저장
|
||||||
|
authService.saveLogin(UUID.fromString(username));
|
||||||
|
|
||||||
|
return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/refresh")
|
@PostMapping("/refresh")
|
||||||
@Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.")
|
@Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.")
|
||||||
@ApiResponses({
|
@ApiResponses({
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "200",
|
responseCode = "200",
|
||||||
description = "재발급 성공",
|
description = "재발급 성공",
|
||||||
content = @Content(schema = @Schema(implementation = TokenResponse.class))),
|
content = @Content(schema = @Schema(implementation = TokenResponse.class))),
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "401",
|
responseCode = "403",
|
||||||
description = "만료되었거나 유효하지 않은 리프레시 토큰",
|
description = "만료되었거나 유효하지 않은 리프레시 토큰",
|
||||||
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||||
})
|
})
|
||||||
public ResponseEntity<TokenResponse> refresh(String refreshToken, HttpServletResponse response)
|
public ResponseEntity<TokenResponse> refresh(String refreshToken, HttpServletResponse response)
|
||||||
throws AccessDeniedException {
|
throws AccessDeniedException {
|
||||||
if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) {
|
if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) {
|
||||||
throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다.");
|
throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = jwtTokenProvider.getSubject(refreshToken);
|
String username = jwtTokenProvider.getSubject(refreshToken);
|
||||||
|
|
||||||
// Redis에 저장된 RefreshToken과 일치하는지 확인
|
// Redis에 저장된 RefreshToken과 일치하는지 확인
|
||||||
@@ -137,32 +179,33 @@ public class AuthController {
|
|||||||
|
|
||||||
// Redis 갱신
|
// Redis 갱신
|
||||||
refreshTokenService.save(
|
refreshTokenService.save(
|
||||||
username, newRefreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
|
username, newRefreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
|
||||||
|
|
||||||
// 쿠키 갱신
|
// 쿠키 갱신
|
||||||
ResponseCookie cookie =
|
ResponseCookie cookie =
|
||||||
ResponseCookie.from(refreshCookieName, newRefreshToken)
|
ResponseCookie.from(refreshCookieName, newRefreshToken)
|
||||||
.httpOnly(true)
|
.httpOnly(true)
|
||||||
.secure(refreshCookieSecure)
|
.secure(refreshCookieSecure)
|
||||||
.path("/")
|
.path("/")
|
||||||
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
|
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
|
||||||
.sameSite("Strict")
|
.sameSite("Strict")
|
||||||
.build();
|
.build();
|
||||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||||
|
|
||||||
return ResponseEntity.ok(new TokenResponse("ACTIVE", newAccessToken, newRefreshToken));
|
MembersDto.Member member = new MembersDto.Member();
|
||||||
|
return ResponseEntity.ok(new TokenResponse("ACTIVE", newAccessToken, newRefreshToken, member));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
@Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(리프레시 토큰 삭제)합니다.")
|
@Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(리프레시 토큰 삭제)합니다.")
|
||||||
@ApiResponses({
|
@ApiResponses({
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "200",
|
responseCode = "200",
|
||||||
description = "로그아웃 성공",
|
description = "로그아웃 성공",
|
||||||
content = @Content(schema = @Schema(implementation = Void.class)))
|
content = @Content(schema = @Schema(implementation = Void.class)))
|
||||||
})
|
})
|
||||||
public ApiResponseDto<ResponseEntity<Object>> logout(
|
public ApiResponseDto<ResponseEntity<Object>> logout(
|
||||||
Authentication authentication, HttpServletResponse response) {
|
Authentication authentication, HttpServletResponse response) {
|
||||||
if (authentication != null) {
|
if (authentication != null) {
|
||||||
String username = authentication.getName();
|
String username = authentication.getName();
|
||||||
// Redis에서 RefreshToken 삭제
|
// Redis에서 RefreshToken 삭제
|
||||||
@@ -171,19 +214,15 @@ public class AuthController {
|
|||||||
|
|
||||||
// 쿠키 삭제 (Max-Age=0)
|
// 쿠키 삭제 (Max-Age=0)
|
||||||
ResponseCookie cookie =
|
ResponseCookie cookie =
|
||||||
ResponseCookie.from(refreshCookieName, "")
|
ResponseCookie.from(refreshCookieName, "")
|
||||||
.httpOnly(true)
|
.httpOnly(true)
|
||||||
.secure(refreshCookieSecure)
|
.secure(refreshCookieSecure)
|
||||||
.path("/")
|
.path("/")
|
||||||
.maxAge(0)
|
.maxAge(0)
|
||||||
.sameSite("Strict")
|
.sameSite("Strict")
|
||||||
.build();
|
.build();
|
||||||
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||||
|
|
||||||
return ApiResponseDto.createOK(ResponseEntity.noContent().build());
|
return ApiResponseDto.createOK(ResponseEntity.noContent().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public record TokenResponse(String status, String accessToken, String refreshToken) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import jakarta.validation.Valid;
|
|||||||
import lombok.RequiredArgsConstructor;
|
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.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;
|
||||||
@@ -27,44 +29,51 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class MembersApiController {
|
public class MembersApiController {
|
||||||
|
|
||||||
|
private final AuthenticationManager authenticationManager;
|
||||||
private final MembersService membersService;
|
private final MembersService membersService;
|
||||||
|
|
||||||
@Operation(summary = "회원정보 목록", description = "회원정보 조회")
|
@Operation(summary = "회원정보 목록", description = "회원정보 조회")
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
value = {
|
value = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "200",
|
responseCode = "200",
|
||||||
description = "검색 성공",
|
description = "검색 성공",
|
||||||
content =
|
content =
|
||||||
@Content(
|
@Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
schema = @Schema(implementation = Page.class))),
|
schema = @Schema(implementation = Page.class))),
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||||
})
|
})
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ApiResponseDto<Page<Basic>> getMemberList(
|
public ApiResponseDto<Page<Basic>> getMemberList(
|
||||||
@ParameterObject MembersDto.SearchReq searchReq) {
|
@ParameterObject MembersDto.SearchReq searchReq) {
|
||||||
return ApiResponseDto.ok(membersService.findByMembers(searchReq));
|
return ApiResponseDto.ok(membersService.findByMembers(searchReq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
@Operation(summary = "사용자 비밀번호 변경", description = "사용자 비밀번호 변경")
|
summary = "사용자 비밀번호 변경",
|
||||||
|
description = "로그인 성공후 status가 INACTIVE일때 로그인 id를 memberId로 path 생성필요")
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
value = {
|
value = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
responseCode = "201",
|
responseCode = "201",
|
||||||
description = "사용자 비밀번호 변경",
|
description = "사용자 비밀번호 변경",
|
||||||
content =
|
content =
|
||||||
@Content(
|
@Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
schema = @Schema(implementation = Long.class))),
|
schema = @Schema(implementation = Long.class))),
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
||||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||||
})
|
})
|
||||||
@PatchMapping("/{memberId}/password")
|
@PatchMapping("/{memberId}/password")
|
||||||
public ApiResponseDto<Long> resetPassword(@PathVariable Long memberId, @RequestBody @Valid MembersDto.InitReq initReq) {
|
public ApiResponseDto<String> resetPassword(
|
||||||
|
@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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.kamco.cd.kamcoback.members.dto;
|
package com.kamco.cd.kamcoback.members.dto;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.common.enums.RoleType;
|
||||||
|
import com.kamco.cd.kamcoback.common.enums.StatusType;
|
||||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||||
|
import com.kamco.cd.kamcoback.config.enums.EnumType;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
@@ -22,47 +25,56 @@ public class MembersDto {
|
|||||||
private Long id;
|
private Long id;
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
private String userRole;
|
private String userRole;
|
||||||
|
private String userRoleName;
|
||||||
private String name;
|
private String name;
|
||||||
private String userId;
|
private String userId;
|
||||||
private String employeeNo;
|
private String employeeNo;
|
||||||
private String tempPassword;
|
private String tempPassword;
|
||||||
private String status;
|
private String status;
|
||||||
@JsonFormatDttm
|
private String statusName;
|
||||||
private ZonedDateTime createdDttm;
|
@JsonFormatDttm private ZonedDateTime createdDttm;
|
||||||
@JsonFormatDttm
|
@JsonFormatDttm private ZonedDateTime updatedDttm;
|
||||||
private ZonedDateTime updatedDttm;
|
@JsonFormatDttm private ZonedDateTime firstLoginDttm;
|
||||||
@JsonFormatDttm
|
@JsonFormatDttm private ZonedDateTime lastLoginDttm;
|
||||||
private ZonedDateTime firstLoginDttm;
|
|
||||||
@JsonFormatDttm
|
|
||||||
private ZonedDateTime lastLoginDttm;
|
|
||||||
|
|
||||||
public Basic(
|
public Basic(
|
||||||
Long id,
|
Long id,
|
||||||
UUID uuid,
|
UUID uuid,
|
||||||
String userRole,
|
String userRole,
|
||||||
String name,
|
String name,
|
||||||
String userId,
|
String userId,
|
||||||
String employeeNo,
|
String employeeNo,
|
||||||
String tempPassword,
|
String tempPassword,
|
||||||
String status,
|
String status,
|
||||||
ZonedDateTime createdDttm,
|
ZonedDateTime createdDttm,
|
||||||
ZonedDateTime updatedDttm,
|
ZonedDateTime updatedDttm,
|
||||||
ZonedDateTime firstLoginDttm,
|
ZonedDateTime firstLoginDttm,
|
||||||
ZonedDateTime lastLoginDttm
|
ZonedDateTime lastLoginDttm) {
|
||||||
) {
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.userRole = userRole;
|
this.userRole = userRole;
|
||||||
|
this.userRoleName = getUserRoleName(userRole);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.employeeNo = employeeNo;
|
this.employeeNo = employeeNo;
|
||||||
this.tempPassword = tempPassword;
|
this.tempPassword = tempPassword;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
this.statusName = getStatusName(status);
|
||||||
this.createdDttm = createdDttm;
|
this.createdDttm = createdDttm;
|
||||||
this.updatedDttm = updatedDttm;
|
this.updatedDttm = updatedDttm;
|
||||||
this.firstLoginDttm = firstLoginDttm;
|
this.firstLoginDttm = firstLoginDttm;
|
||||||
this.lastLoginDttm = lastLoginDttm;
|
this.lastLoginDttm = lastLoginDttm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getUserRoleName(String roleId) {
|
||||||
|
RoleType type = EnumType.fromId(RoleType.class, roleId);
|
||||||
|
return type.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStatusName(String status) {
|
||||||
|
StatusType type = EnumType.fromId(StatusType.class, status);
|
||||||
|
return type.getText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -71,7 +83,9 @@ public class MembersDto {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public static class SearchReq {
|
public static class SearchReq {
|
||||||
|
|
||||||
@Schema(description = "전체, 관리자(ROLE_ADMIN), 라벨러(ROLE_LABELER), 검수자(ROLE_REVIEWER)", example = "")
|
@Schema(
|
||||||
|
description = "전체, 관리자(ROLE_ADMIN), 라벨러(ROLE_LABELER), 검수자(ROLE_REVIEWER)",
|
||||||
|
example = "")
|
||||||
private String userRole;
|
private String userRole;
|
||||||
|
|
||||||
@Schema(description = "키워드", example = "홍길동")
|
@Schema(description = "키워드", example = "홍길동")
|
||||||
@@ -115,7 +129,7 @@ public class MembersDto {
|
|||||||
private String employeeNo;
|
private String employeeNo;
|
||||||
|
|
||||||
public AddReq(
|
public AddReq(
|
||||||
String userRole, String name, String userId, String tempPassword, String employeeNo) {
|
String userRole, String name, String userId, String tempPassword, String employeeNo) {
|
||||||
this.userRole = userRole;
|
this.userRole = userRole;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
@@ -160,4 +174,15 @@ public class MembersDto {
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
private String tempPassword;
|
private String tempPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class Member {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private String employeeNo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import lombok.ToString;
|
|||||||
@ToString(exclude = "password")
|
@ToString(exclude = "password")
|
||||||
public class SignInRequest {
|
public class SignInRequest {
|
||||||
|
|
||||||
@Schema(description = "사용자 ID", example = "admin")
|
@Schema(description = "사용자 ID", example = "admin2")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
@Schema(description = "비밀번호", example = "kamco1234!")
|
@Schema(description = "비밀번호", example = "Admin2!@#")
|
||||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||||
private String password;
|
private String password;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.kamco.cd.kamcoback.members.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TokenResponse {
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
private String accessToken;
|
||||||
|
private String refreshToken;
|
||||||
|
private MembersDto.Member member;
|
||||||
|
}
|
||||||
@@ -36,7 +36,6 @@ public class AdminService {
|
|||||||
membersCoreService.updateMembers(uuid, updateReq);
|
membersCoreService.updateMembers(uuid, updateReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 관리자 계정 미사용 처리
|
* 관리자 계정 미사용 처리
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -24,16 +24,6 @@ public class AuthService {
|
|||||||
membersCoreService.saveLogin(uuid);
|
membersCoreService.saveLogin(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 로그인 실패 저장
|
|
||||||
*
|
|
||||||
* @param uuid
|
|
||||||
*/
|
|
||||||
@Transactional
|
|
||||||
public void loginFail(UUID uuid) {
|
|
||||||
membersCoreService.loginFail(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용자 상태 조회
|
* 사용자 상태 조회
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ public class MembersService {
|
|||||||
* @param initReq
|
* @param initReq
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void resetPassword(Long id, MembersDto.InitReq initReq) {
|
public void resetPassword(String id, MembersDto.InitReq initReq) {
|
||||||
|
|
||||||
if (!isValidPassword(initReq.getPassword())) {
|
if (!isValidPassword(initReq.getPassword())) {
|
||||||
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
|
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
@@ -43,13 +44,14 @@ public class MembersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 대문자 1개 이상 소문자 1개 이상 숫자 1개 이상 특수문자(!@#$) 1개 이상
|
* 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호
|
||||||
*
|
*
|
||||||
* @param password
|
* @param password
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private boolean isValidPassword(String password) {
|
private boolean isValidPassword(String password) {
|
||||||
String regex = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[!@#$]).{8,20}$";
|
String passwordPattern =
|
||||||
return Pattern.matches(regex, password);
|
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$";
|
||||||
|
return Pattern.matches(passwordPattern, password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.core;
|
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.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;
|
||||||
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
||||||
@@ -8,7 +10,6 @@ import com.kamco.cd.kamcoback.members.dto.SignInRequest;
|
|||||||
import com.kamco.cd.kamcoback.members.exception.MemberException.DuplicateMemberException;
|
import com.kamco.cd.kamcoback.members.exception.MemberException.DuplicateMemberException;
|
||||||
import com.kamco.cd.kamcoback.members.exception.MemberException.DuplicateMemberException.Field;
|
import com.kamco.cd.kamcoback.members.exception.MemberException.DuplicateMemberException.Field;
|
||||||
import com.kamco.cd.kamcoback.members.exception.MemberException.MemberNotFoundException;
|
import com.kamco.cd.kamcoback.members.exception.MemberException.MemberNotFoundException;
|
||||||
import com.kamco.cd.kamcoback.members.exception.MemberException.PasswordNotFoundException;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||||
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
|
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
@@ -24,6 +25,7 @@ import org.springframework.stereotype.Service;
|
|||||||
public class MembersCoreService {
|
public class MembersCoreService {
|
||||||
|
|
||||||
private final MembersRepository membersRepository;
|
private final MembersRepository membersRepository;
|
||||||
|
private final UserUtil userUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 관리자 계정 등록
|
* 관리자 계정 등록
|
||||||
@@ -48,6 +50,7 @@ public class MembersCoreService {
|
|||||||
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());
|
||||||
|
|
||||||
return membersRepository.save(memberEntity).getId();
|
return membersRepository.save(memberEntity).getId();
|
||||||
}
|
}
|
||||||
@@ -60,7 +63,7 @@ public class MembersCoreService {
|
|||||||
*/
|
*/
|
||||||
public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) {
|
public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) {
|
||||||
MemberEntity memberEntity =
|
MemberEntity memberEntity =
|
||||||
membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new);
|
membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new);
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(updateReq.getName())) {
|
if (StringUtils.isNotBlank(updateReq.getName())) {
|
||||||
memberEntity.setName(updateReq.getName());
|
memberEntity.setName(updateReq.getName());
|
||||||
@@ -75,48 +78,47 @@ public class MembersCoreService {
|
|||||||
memberEntity.setEmployeeNo(updateReq.getEmployeeNo());
|
memberEntity.setEmployeeNo(updateReq.getEmployeeNo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memberEntity.setUpdtrUid(userUtil.getId());
|
||||||
|
|
||||||
membersRepository.save(memberEntity);
|
membersRepository.save(memberEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 관리자 계정 미사용 처리
|
* 관리자 계정 삭제 처리
|
||||||
*
|
*
|
||||||
* @param uuid
|
* @param uuid
|
||||||
*/
|
*/
|
||||||
public void deleteAccount(UUID uuid) {
|
public void deleteAccount(UUID uuid) {
|
||||||
MemberEntity memberEntity =
|
MemberEntity memberEntity =
|
||||||
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
||||||
|
|
||||||
memberEntity.setStatus("INACTIVE");
|
memberEntity.setStatus(StatusType.DELETED.getId());
|
||||||
memberEntity.setUpdatedDttm(ZonedDateTime.now());
|
memberEntity.setUpdatedDttm(ZonedDateTime.now());
|
||||||
|
memberEntity.setUpdtrUid(userUtil.getId());
|
||||||
membersRepository.save(memberEntity);
|
membersRepository.save(memberEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 패스워드 변경
|
* 패스워드 변경
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id
|
||||||
*/
|
*/
|
||||||
public void resetPassword(Long id, MembersDto.InitReq initReq) {
|
public void resetPassword(String id, MembersDto.InitReq initReq) {
|
||||||
MemberEntity memberEntity =
|
MemberEntity memberEntity =
|
||||||
membersRepository.findById(id).orElseThrow(() -> new MemberNotFoundException());
|
membersRepository.findByUserId(id).orElseThrow(() -> new MemberNotFoundException());
|
||||||
|
|
||||||
if (!memberEntity.getTempPassword().equals(initReq.getTempPassword())) {
|
|
||||||
throw new PasswordNotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
String salt =
|
String salt =
|
||||||
BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim());
|
BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim());
|
||||||
// 패스워드 암호화
|
// 패스워드 암호화
|
||||||
String hashedPassword = BCrypt.hashpw(initReq.getPassword(), salt);
|
String hashedPassword = BCrypt.hashpw(initReq.getPassword(), salt);
|
||||||
|
|
||||||
memberEntity.setPassword(hashedPassword);
|
memberEntity.setPassword(hashedPassword);
|
||||||
memberEntity.setStatus("ACTIVE");
|
memberEntity.setStatus("ACTIVE");
|
||||||
memberEntity.setUpdatedDttm(ZonedDateTime.now());
|
memberEntity.setUpdatedDttm(ZonedDateTime.now());
|
||||||
|
memberEntity.setUpdtrUid(memberEntity.getId());
|
||||||
membersRepository.save(memberEntity);
|
membersRepository.save(memberEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,6 +131,20 @@ public class MembersCoreService {
|
|||||||
return membersRepository.findByMembers(searchReq);
|
return membersRepository.findByMembers(searchReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자 상태 조회
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getUserStatus(SignInRequest request) {
|
||||||
|
MemberEntity memberEntity =
|
||||||
|
membersRepository
|
||||||
|
.findByUserId(request.getUsername())
|
||||||
|
.orElseThrow(MemberNotFoundException::new);
|
||||||
|
return memberEntity.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 최초 로그인 저장 마지막 로그인 저장
|
* 최초 로그인 저장 마지막 로그인 저장
|
||||||
*
|
*
|
||||||
@@ -136,7 +152,7 @@ public class MembersCoreService {
|
|||||||
*/
|
*/
|
||||||
public void saveLogin(UUID uuid) {
|
public void saveLogin(UUID uuid) {
|
||||||
MemberEntity memberEntity =
|
MemberEntity memberEntity =
|
||||||
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
||||||
|
|
||||||
if (memberEntity.getFirstLoginDttm() == null) {
|
if (memberEntity.getFirstLoginDttm() == null) {
|
||||||
memberEntity.setFirstLoginDttm(ZonedDateTime.now());
|
memberEntity.setFirstLoginDttm(ZonedDateTime.now());
|
||||||
@@ -145,33 +161,4 @@ public class MembersCoreService {
|
|||||||
memberEntity.setLoginFailCount(0);
|
memberEntity.setLoginFailCount(0);
|
||||||
membersRepository.save(memberEntity);
|
membersRepository.save(memberEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 로그인 실패시 상태 저장
|
|
||||||
*
|
|
||||||
* @param uuid
|
|
||||||
*/
|
|
||||||
public void loginFail(UUID uuid) {
|
|
||||||
MemberEntity memberEntity =
|
|
||||||
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
|
||||||
|
|
||||||
int failCnt = memberEntity.getLoginFailCount() + 1;
|
|
||||||
if (failCnt >= 5) {
|
|
||||||
memberEntity.setStatus("INACTIVE");
|
|
||||||
}
|
|
||||||
|
|
||||||
memberEntity.setLoginFailCount(failCnt);
|
|
||||||
membersRepository.save(memberEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 사용자 상태 조회
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getUserStatus(SignInRequest request) {
|
|
||||||
MemberEntity memberEntity = membersRepository.findByUserId(request.getUsername()).orElseThrow(MemberNotFoundException::new);
|
|
||||||
return memberEntity.getStatus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,7 @@ package com.kamco.cd.kamcoback.postgres.entity;
|
|||||||
|
|
||||||
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
|
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
|
||||||
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
|
||||||
import jakarta.persistence.CascadeType;
|
import jakarta.persistence.*;
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.GenerationType;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.OneToMany;
|
|
||||||
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.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
@@ -21,6 +11,7 @@ import java.util.List;
|
|||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.Where;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Getter
|
@Getter
|
||||||
@@ -60,6 +51,7 @@ public class CommonCodeEntity extends CommonDateEntity {
|
|||||||
private CommonCodeEntity parent;
|
private CommonCodeEntity parent;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
|
@Where(clause = "deleted = false or deleted is null")
|
||||||
private List<CommonCodeEntity> children = new ArrayList<>();
|
private List<CommonCodeEntity> children = new ArrayList<>();
|
||||||
|
|
||||||
@Size(max = 255)
|
@Size(max = 255)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.entity;
|
package com.kamco.cd.kamcoback.postgres.entity;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.common.enums.StatusType;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
@@ -63,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 = "INACTIVE";
|
private String status = StatusType.INACTIVE.getId();
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@ColumnDefault("now()")
|
@ColumnDefault("now()")
|
||||||
@@ -83,4 +84,10 @@ public class MemberEntity {
|
|||||||
@Column(name = "login_fail_count")
|
@Column(name = "login_fail_count")
|
||||||
@ColumnDefault("0")
|
@ColumnDefault("0")
|
||||||
private Integer loginFailCount = 0;
|
private Integer loginFailCount = 0;
|
||||||
|
|
||||||
|
@Column(name = "rgstr_uid")
|
||||||
|
private Long rgstrUidl;
|
||||||
|
|
||||||
|
@Column(name = "updtr_uid")
|
||||||
|
private Long updtrUid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,18 +20,8 @@ public class CommonCodeRepositoryImpl implements CommonCodeRepositoryCustom {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<CommonCodeEntity> findByCodeId(Long id) {
|
public Optional<CommonCodeEntity> findByCodeId(Long id) {
|
||||||
QCommonCodeEntity child = new QCommonCodeEntity("child");
|
|
||||||
return Optional.ofNullable(
|
return Optional.ofNullable(
|
||||||
queryFactory
|
queryFactory.selectFrom(commonCodeEntity).where(commonCodeEntity.id.eq(id)).fetchOne());
|
||||||
.selectFrom(commonCodeEntity)
|
|
||||||
.leftJoin(commonCodeEntity.children, child)
|
|
||||||
.fetchJoin()
|
|
||||||
.where(
|
|
||||||
commonCodeEntity.id.eq(id),
|
|
||||||
commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull()),
|
|
||||||
child.deleted.isFalse().or(child.deleted.isNull()))
|
|
||||||
.orderBy(commonCodeEntity.order.asc(), child.order.asc())
|
|
||||||
.fetchOne());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -41,13 +31,12 @@ public class CommonCodeRepositoryImpl implements CommonCodeRepositoryCustom {
|
|||||||
queryFactory
|
queryFactory
|
||||||
.selectFrom(commonCodeEntity)
|
.selectFrom(commonCodeEntity)
|
||||||
.leftJoin(commonCodeEntity.children, child)
|
.leftJoin(commonCodeEntity.children, child)
|
||||||
.fetchJoin()
|
.on(child.deleted.isFalse().or(child.deleted.isNull()))
|
||||||
.where(
|
.where(
|
||||||
commonCodeEntity.parent.isNull(),
|
commonCodeEntity.parent.isNull(),
|
||||||
commonCodeEntity.code.eq(code),
|
commonCodeEntity.code.eq(code),
|
||||||
commonCodeEntity.used.isTrue(),
|
commonCodeEntity.used.isTrue(),
|
||||||
commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull()),
|
commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull()))
|
||||||
child.deleted.isFalse().or(child.deleted.isNull()))
|
|
||||||
.orderBy(child.order.asc())
|
.orderBy(child.order.asc())
|
||||||
.fetchOne());
|
.fetchOne());
|
||||||
}
|
}
|
||||||
@@ -61,8 +50,7 @@ public class CommonCodeRepositoryImpl implements CommonCodeRepositoryCustom {
|
|||||||
.on(child.deleted.isFalse().or(child.deleted.isNull()))
|
.on(child.deleted.isFalse().or(child.deleted.isNull()))
|
||||||
.where(
|
.where(
|
||||||
commonCodeEntity.parent.isNull(),
|
commonCodeEntity.parent.isNull(),
|
||||||
commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull())
|
commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull()))
|
||||||
)
|
|
||||||
.orderBy(commonCodeEntity.order.asc(), child.order.asc())
|
.orderBy(commonCodeEntity.order.asc(), child.order.asc())
|
||||||
.fetch();
|
.fetch();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
|||||||
@Override
|
@Override
|
||||||
public boolean existsByUserId(String userId) {
|
public boolean existsByUserId(String userId) {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectOne()
|
.selectOne()
|
||||||
.from(memberEntity)
|
.from(memberEntity)
|
||||||
.where(memberEntity.userId.eq(userId))
|
.where(memberEntity.userId.eq(userId))
|
||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
!= null;
|
!= null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +49,7 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
|||||||
@Override
|
@Override
|
||||||
public Optional<MemberEntity> findByUserId(String userId) {
|
public Optional<MemberEntity> findByUserId(String userId) {
|
||||||
return Optional.ofNullable(
|
return Optional.ofNullable(
|
||||||
queryFactory.selectFrom(memberEntity).where(memberEntity.userId.eq(userId)).fetchOne());
|
queryFactory.selectFrom(memberEntity).where(memberEntity.userId.eq(userId)).fetchOne());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,10 +68,11 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
|||||||
String contains = "%" + searchReq.getKeyword() + "%";
|
String contains = "%" + searchReq.getKeyword() + "%";
|
||||||
|
|
||||||
builder.and(
|
builder.and(
|
||||||
memberEntity.name.likeIgnoreCase(contains)
|
memberEntity
|
||||||
.or(memberEntity.userId.likeIgnoreCase(contains))
|
.name
|
||||||
.or(memberEntity.employeeNo.likeIgnoreCase(contains))
|
.likeIgnoreCase(contains)
|
||||||
);
|
.or(memberEntity.userId.likeIgnoreCase(contains))
|
||||||
|
.or(memberEntity.employeeNo.likeIgnoreCase(contains)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 권한
|
// 권한
|
||||||
@@ -80,35 +81,30 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<MembersDto.Basic> content =
|
List<MembersDto.Basic> content =
|
||||||
queryFactory
|
queryFactory
|
||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
MembersDto.Basic.class,
|
MembersDto.Basic.class,
|
||||||
memberEntity.id,
|
memberEntity.id,
|
||||||
memberEntity.uuid,
|
memberEntity.uuid,
|
||||||
memberEntity.userRole,
|
memberEntity.userRole,
|
||||||
memberEntity.name,
|
memberEntity.name,
|
||||||
memberEntity.userId,
|
memberEntity.userId,
|
||||||
memberEntity.employeeNo,
|
memberEntity.employeeNo,
|
||||||
memberEntity.tempPassword,
|
memberEntity.tempPassword,
|
||||||
memberEntity.status,
|
memberEntity.status,
|
||||||
memberEntity.createdDttm,
|
memberEntity.createdDttm,
|
||||||
memberEntity.updatedDttm,
|
memberEntity.updatedDttm,
|
||||||
memberEntity.firstLoginDttm,
|
memberEntity.firstLoginDttm,
|
||||||
memberEntity.lastLoginDttm
|
memberEntity.lastLoginDttm))
|
||||||
))
|
.from(memberEntity)
|
||||||
.from(memberEntity)
|
.where(builder)
|
||||||
.where(builder)
|
.offset(pageable.getOffset())
|
||||||
.offset(pageable.getOffset())
|
.limit(pageable.getPageSize())
|
||||||
.limit(pageable.getPageSize())
|
.orderBy(memberEntity.createdDttm.desc())
|
||||||
.orderBy(memberEntity.createdDttm.desc())
|
.fetch();
|
||||||
.fetch();
|
|
||||||
|
|
||||||
long total =
|
long total = queryFactory.select(memberEntity).from(memberEntity).fetchCount();
|
||||||
queryFactory
|
|
||||||
.select(memberEntity)
|
|
||||||
.from(memberEntity)
|
|
||||||
.fetchCount();
|
|
||||||
|
|
||||||
return new PageImpl<>(content, pageable, total);
|
return new PageImpl<>(content, pageable, total);
|
||||||
}
|
}
|
||||||
@@ -122,6 +118,6 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
|||||||
@Override
|
@Override
|
||||||
public Optional<MemberEntity> findByUUID(UUID uuid) {
|
public Optional<MemberEntity> findByUUID(UUID uuid) {
|
||||||
return Optional.ofNullable(
|
return Optional.ofNullable(
|
||||||
queryFactory.selectFrom(memberEntity).where(memberEntity.uuid.eq(uuid)).fetchOne());
|
queryFactory.selectFrom(memberEntity).where(memberEntity.uuid.eq(uuid)).fetchOne());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.kamco.cd.kamcoback.test;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.ErrorResponse;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "test api", description = "test api")
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/api/test")
|
||||||
|
public class TestApiController {
|
||||||
|
|
||||||
|
@Operation(summary = "admin test", description = "admin test api")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "조회",
|
||||||
|
content = @Content(schema = @Schema(implementation = String.class))),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "403",
|
||||||
|
description = "권한 없음",
|
||||||
|
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||||
|
})
|
||||||
|
@GetMapping("/admin")
|
||||||
|
public String admin() {
|
||||||
|
return "I am administrator";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "label test", description = "label test api")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "조회",
|
||||||
|
content = @Content(schema = @Schema(implementation = String.class))),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "403",
|
||||||
|
description = "권한 없음",
|
||||||
|
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||||
|
})
|
||||||
|
@GetMapping("/label")
|
||||||
|
public String label() {
|
||||||
|
return "Labeling is available.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "review test", description = "review test api")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "조회",
|
||||||
|
content = @Content(schema = @Schema(implementation = String.class))),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "403",
|
||||||
|
description = "권한 없음",
|
||||||
|
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||||
|
})
|
||||||
|
@GetMapping("/review")
|
||||||
|
public String review() {
|
||||||
|
return "Review is available.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,8 +38,10 @@ spring:
|
|||||||
|
|
||||||
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일
|
||||||
refresh-token-validity-in-ms: 604800000 # 7일
|
#refresh-token-validity-in-ms: 604800000 # 7일
|
||||||
|
access-token-validity-in-ms: 60000 # 1분
|
||||||
|
refresh-token-validity-in-ms: 300000 # 5분
|
||||||
|
|
||||||
token:
|
token:
|
||||||
refresh-cookie-name: kamco-dev # 개발용 쿠키 이름
|
refresh-cookie-name: kamco-dev # 개발용 쿠키 이름
|
||||||
|
|||||||
Reference in New Issue
Block a user