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

This commit is contained in:
Moon
2025-12-11 17:16:58 +09:00
30 changed files with 609 additions and 372 deletions

View File

@@ -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();
}
}

View File

@@ -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());

View File

@@ -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;
}
} }

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
} }

View File

@@ -0,0 +1,10 @@
package com.kamco.cd.kamcoback.common.utils;
import org.springframework.http.HttpStatus;
public interface ErrorCode {
String getCode();
HttpStatus getStatus();
}

View File

@@ -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;
}
}

View File

@@ -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);
} }
} }

View File

@@ -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;
@@ -40,21 +42,21 @@ public class SecurityConfig {
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();

View File

@@ -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"

View File

@@ -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
}
} }

View File

@@ -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 {

View File

@@ -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;
@@ -55,12 +59,42 @@ public class AuthController {
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(
@@ -75,15 +109,16 @@ public class AuthController {
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);
@@ -103,7 +138,15 @@ public class AuthController {
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")
@@ -114,7 +157,7 @@ public class AuthController {
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)))
}) })
@@ -123,7 +166,6 @@ public class AuthController {
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과 일치하는지 확인
@@ -150,7 +192,8 @@ public class AuthController {
.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")
@@ -182,8 +225,4 @@ public class AuthController {
return ApiResponseDto.createOK(ResponseEntity.noContent().build()); return ApiResponseDto.createOK(ResponseEntity.noContent().build());
} }
public record TokenResponse(String status, String accessToken, String refreshToken) {
}
} }

View File

@@ -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,6 +29,7 @@ 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 = "회원정보 조회")
@@ -48,8 +51,9 @@ public class MembersApiController {
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(
@@ -64,7 +68,12 @@ public class MembersApiController {
@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);
} }

View File

@@ -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,19 +25,17 @@ 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,
@@ -48,21 +49,32 @@ public class MembersDto {
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 = "홍길동")
@@ -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;
}
} }

View File

@@ -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;
} }

View File

@@ -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;
}

View File

@@ -36,7 +36,6 @@ public class AdminService {
membersCoreService.updateMembers(uuid, updateReq); membersCoreService.updateMembers(uuid, updateReq);
} }
/** /**
* 관리자 계정 미사용 처리 * 관리자 계정 미사용 처리
* *

View File

@@ -24,16 +24,6 @@ public class AuthService {
membersCoreService.saveLogin(uuid); membersCoreService.saveLogin(uuid);
} }
/**
* 로그인 실패 저장
*
* @param uuid
*/
@Transactional
public void loginFail(UUID uuid) {
membersCoreService.loginFail(uuid);
}
/** /**
* 사용자 상태 조회 * 사용자 상태 조회
* *

View File

@@ -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);
} }
} }

View File

@@ -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();
} }
@@ -75,12 +78,13 @@ 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
*/ */
@@ -88,24 +92,20 @@ public class MembersCoreService {
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());
@@ -115,8 +115,10 @@ public class MembersCoreService {
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();
}
/** /**
* 최초 로그인 저장 마지막 로그인 저장 * 최초 로그인 저장 마지막 로그인 저장
* *
@@ -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();
}
} }

View File

@@ -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)

View File

@@ -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;
} }

View File

@@ -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();
} }

View File

@@ -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
.name
.likeIgnoreCase(contains)
.or(memberEntity.userId.likeIgnoreCase(contains)) .or(memberEntity.userId.likeIgnoreCase(contains))
.or(memberEntity.employeeNo.likeIgnoreCase(contains)) .or(memberEntity.employeeNo.likeIgnoreCase(contains)));
);
} }
// 권한 // 권한
@@ -95,8 +96,7 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
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())
@@ -104,11 +104,7 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
.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);
} }

View File

@@ -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.";
}
}

View File

@@ -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 # 개발용 쿠키 이름