diff --git a/src/main/java/clap/server/adapter/inbound/security/filter/LoginAttemptFilter.java b/src/main/java/clap/server/adapter/inbound/security/filter/LoginAttemptFilter.java index f73c54c9..c011a3d3 100644 --- a/src/main/java/clap/server/adapter/inbound/security/filter/LoginAttemptFilter.java +++ b/src/main/java/clap/server/adapter/inbound/security/filter/LoginAttemptFilter.java @@ -2,13 +2,13 @@ import clap.server.application.service.auth.LoginAttemptService; import clap.server.exception.AuthException; -import clap.server.exception.code.GlobalErrorCode; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; @@ -18,7 +18,7 @@ import java.util.ArrayList; import static clap.server.adapter.inbound.security.WebSecurityUrl.LOGIN_ENDPOINT; -import static clap.server.common.constants.AuthConstants.SESSION_ID; +import static clap.server.common.utils.ClientIpParseUtil.getClientIp; @RequiredArgsConstructor @@ -30,13 +30,19 @@ public class LoginAttemptFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String sessionId = request.getHeader(SESSION_ID.getValue().toLowerCase()); + try { + if (request.getRequestURI().equals(LOGIN_ENDPOINT)) { + String clientIp = getClientIp(request); + + loginAttemptService.checkAccountIsLocked(clientIp); - if (request.getRequestURI().equals(LOGIN_ENDPOINT)) { - if (sessionId == null) { - throw new AuthException(GlobalErrorCode.BAD_REQUEST); } - loginAttemptService.checkAccountIsLocked(sessionId); + } catch (AuthException e) { + log.warn("Authentication failed for IP: {}. Error: {}", getClientIp(request), e.getMessage()); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write(e.getErrorCode().getCustomCode()); + log.info("Sent error response: {}", e.getErrorCode().getCustomCode()); + return; } UsernamePasswordAuthenticationToken authenticationToken = diff --git a/src/main/java/clap/server/adapter/inbound/security/handler/JwtAuthenticationEntryPoint.java b/src/main/java/clap/server/adapter/inbound/security/handler/JwtAuthenticationEntryPoint.java index 74ba4fc2..2dfdbe5d 100644 --- a/src/main/java/clap/server/adapter/inbound/security/handler/JwtAuthenticationEntryPoint.java +++ b/src/main/java/clap/server/adapter/inbound/security/handler/JwtAuthenticationEntryPoint.java @@ -23,7 +23,6 @@ public void commence( ) throws IOException, ServletException { AuthErrorCode errorCode = AuthErrorCode.UNAUTHORIZED; - response.setContentType("application/json;charset=UTF-8"); response.setStatus(errorCode.getHttpStatus().value()); response.getWriter().write(errorCode.getCustomCode()); } diff --git a/src/main/java/clap/server/adapter/inbound/web/auth/AuthController.java b/src/main/java/clap/server/adapter/inbound/web/auth/AuthController.java index 5f4b5366..5325a16d 100644 --- a/src/main/java/clap/server/adapter/inbound/web/auth/AuthController.java +++ b/src/main/java/clap/server/adapter/inbound/web/auth/AuthController.java @@ -32,11 +32,11 @@ public class AuthController { @LogType(LogStatus.LOGIN) @Operation(summary = "로그인 API") @PostMapping("/login") - public ResponseEntity login(@RequestHeader(name = "sessionId") String sessionId, + public ResponseEntity login( @RequestBody LoginRequest request, HttpServletRequest httpRequest) { String clientIp = getClientIp(httpRequest); - LoginResponse response = loginUsecase.login(request.nickname(), request.password(), sessionId, clientIp); + LoginResponse response = loginUsecase.login(request.nickname(), request.password(), clientIp); return ResponseEntity.ok(response); } diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/log/response/AnonymousLogResponse.java b/src/main/java/clap/server/adapter/inbound/web/dto/log/response/AnonymousLogResponse.java index f239d289..c0a17695 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/log/response/AnonymousLogResponse.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/log/response/AnonymousLogResponse.java @@ -16,6 +16,6 @@ public record AnonymousLogResponse( String clientIp, @NotBlank Integer statusCode, - int failedAttempts + String failedAttempts ) { } diff --git a/src/main/java/clap/server/adapter/inbound/web/task/ChangeTaskController.java b/src/main/java/clap/server/adapter/inbound/web/task/ChangeTaskController.java index a3e9ffb6..ac78792b 100644 --- a/src/main/java/clap/server/adapter/inbound/web/task/ChangeTaskController.java +++ b/src/main/java/clap/server/adapter/inbound/web/task/ChangeTaskController.java @@ -5,7 +5,6 @@ import clap.server.adapter.inbound.web.dto.task.request.UpdateTaskLabelRequest; import clap.server.adapter.inbound.web.dto.task.request.UpdateTaskProcessorRequest; import clap.server.adapter.inbound.web.dto.task.response.ApprovalTaskResponse; -import clap.server.adapter.inbound.web.dto.task.response.UpdateTaskResponse; import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import clap.server.application.port.inbound.task.ApprovalTaskUsecase; import clap.server.application.port.inbound.task.UpdateTaskLabelUsecase; @@ -40,34 +39,33 @@ public class ChangeTaskController { @Operation(summary = "작업 상태 변경") @Secured("ROLE_MANAGER") @PatchMapping("/{taskId}/status") - public ResponseEntity updateTaskState( + public void updateTaskState( @PathVariable @NotNull Long taskId, @AuthenticationPrincipal SecurityUserDetails userInfo, @Parameter(description = "변경하고 싶은 작업 상태", schema = @Schema(allowableValues = {"IN_PROGRESS", "PENDING_COMPLETED", "COMPLETED"})) @RequestBody TaskStatus taskStatus) { - - return ResponseEntity.ok(updateTaskStatusUsecase.updateTaskStatus(userInfo.getUserId(), taskId, taskStatus)); + updateTaskStatusUsecase.updateTaskStatus(userInfo.getUserId(), taskId, taskStatus); } @Operation(summary = "작업 처리자 변경") @Secured({"ROLE_MANAGER"}) @PatchMapping("/{taskId}/processor") - public ResponseEntity updateTaskProcessor( + public void updateTaskProcessor( @PathVariable Long taskId, @AuthenticationPrincipal SecurityUserDetails userInfo, @Valid @RequestBody UpdateTaskProcessorRequest updateTaskProcessorRequest) { - return ResponseEntity.ok(updateTaskProcessorUsecase.updateTaskProcessor(taskId, userInfo.getUserId(), updateTaskProcessorRequest)); + updateTaskProcessorUsecase.updateTaskProcessor(taskId, userInfo.getUserId(), updateTaskProcessorRequest); } @Operation(summary = "작업 구분 변경") @Secured({"ROLE_MANAGER"}) @PatchMapping("/{taskId}/label") - public ResponseEntity updateTaskLabel( + public void updateTaskLabel( @PathVariable Long taskId, @AuthenticationPrincipal SecurityUserDetails userInfo, @Valid @RequestBody UpdateTaskLabelRequest updateTaskLabelRequest) { - return ResponseEntity.ok(updateTaskLabelUsecase.updateTaskLabel(taskId, userInfo.getUserId(), updateTaskLabelRequest)); + updateTaskLabelUsecase.updateTaskLabel(taskId, userInfo.getUserId(), updateTaskLabelRequest); } @Operation(summary = "작업 승인") diff --git a/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java b/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java index 84cda3b0..be475dd8 100644 --- a/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java +++ b/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java @@ -46,11 +46,11 @@ public ResponseEntity createTask( @Operation(summary = "작업 수정") @PatchMapping(value = "/{taskId}", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}) @Secured({"ROLE_MANAGER", "ROLE_USER"}) - public ResponseEntity updateTask( + public void updateTask( @PathVariable @NotNull Long taskId, @RequestPart(name = "taskInfo") @Valid UpdateTaskRequest updateTaskRequest, @RequestPart(name = "attachment", required = false) List attachments, @AuthenticationPrincipal SecurityUserDetails userInfo){ - return ResponseEntity.ok(updateTaskUsecase.updateTask(userInfo.getUserId(), taskId, updateTaskRequest, attachments)); + updateTaskUsecase.updateTask(userInfo.getUserId(), taskId, updateTaskRequest, attachments); } } diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java index b2643307..3fd24e89 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java @@ -21,11 +21,11 @@ public void save(LoginLog loginLog) { loginLogRepository.save(loginLogEntity); } - public void deleteById(String sessionId) { - loginLogRepository.deleteById(sessionId); + public void deleteById(String clientIp) { + loginLogRepository.deleteById(clientIp); } - public Optional findBySessionId(String sessionId) { - return loginLogRepository.findById(sessionId).map(loginLogMapper::toDomain); + public Optional findByClientIp(String clientIp) { + return loginLogRepository.findById(clientIp).map(loginLogMapper::toDomain); } } diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogEntity.java b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogEntity.java index 6993517c..fda27839 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogEntity.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogEntity.java @@ -10,29 +10,26 @@ import lombok.ToString; import org.springframework.data.annotation.Id; import org.springframework.data.redis.core.RedisHash; -import org.springframework.data.redis.core.index.Indexed; import java.time.LocalDateTime; @Getter @RedisHash("loginLog") @Builder -@ToString(of = {"sessionId", "clientIp", "attemptNickname", "lastAttemptAt", "attemptCount", "isLocked"}) -@EqualsAndHashCode(of = {"sessionId"}) +@ToString(of = {"clientIp", "attemptNickname", "lastAttemptAt", "failedCount", "isLocked"}) +@EqualsAndHashCode(of = {"clientIp"}) public class LoginLogEntity { @Id - private String sessionId; - private String clientIp; private String attemptNickname; - @JsonSerialize(using = ToStringSerializer.class) // 직렬화 방식을 설정 + @JsonSerialize(using = ToStringSerializer.class) @JsonDeserialize(using = LocalDateTimeDeserializer.class) @Builder.Default private LocalDateTime lastAttemptAt = LocalDateTime.now(); - private int attemptCount; + private int failedCount; private boolean isLocked; } diff --git a/src/main/java/clap/server/application/mapper/LogMapper.java b/src/main/java/clap/server/application/mapper/LogMapper.java index 0899c673..04e494ae 100644 --- a/src/main/java/clap/server/application/mapper/LogMapper.java +++ b/src/main/java/clap/server/application/mapper/LogMapper.java @@ -6,7 +6,7 @@ import clap.server.domain.model.log.MemberLog; public class LogMapper { - public static AnonymousLogResponse toAnonymousLogResponse(AnonymousLog anonymousLog, int failedAttempts) { + public static AnonymousLogResponse toAnonymousLogResponse(AnonymousLog anonymousLog) { return new AnonymousLogResponse( anonymousLog.getLogId(), anonymousLog.getLogStatus(), @@ -14,7 +14,7 @@ public static AnonymousLogResponse toAnonymousLogResponse(AnonymousLog anonymous anonymousLog.getLoginNickname(), anonymousLog.getClientIp(), anonymousLog.getStatusCode(), - failedAttempts + anonymousLog.getResponseBody() ); } public static MemberLogResponse toMemberLogResponse(MemberLog memberLog) { diff --git a/src/main/java/clap/server/application/mapper/NotificationMapper.java b/src/main/java/clap/server/application/mapper/NotificationMapper.java index fdacb40f..4cc1b150 100644 --- a/src/main/java/clap/server/application/mapper/NotificationMapper.java +++ b/src/main/java/clap/server/application/mapper/NotificationMapper.java @@ -17,6 +17,7 @@ public static FindNotificationListResponse toFindNoticeListResponse(Notification notification.getReceiver().getMemberId(), notification.getTask().getTitle(), notification.getMessage() != null ? notification.getMessage() : null, + notification.isRead(), notification.getCreatedAt() ); } diff --git a/src/main/java/clap/server/application/port/inbound/auth/LoginUsecase.java b/src/main/java/clap/server/application/port/inbound/auth/LoginUsecase.java index 4a88128c..5861cfe7 100644 --- a/src/main/java/clap/server/application/port/inbound/auth/LoginUsecase.java +++ b/src/main/java/clap/server/application/port/inbound/auth/LoginUsecase.java @@ -3,5 +3,5 @@ import clap.server.adapter.inbound.web.dto.auth.response.LoginResponse; public interface LoginUsecase { - LoginResponse login(String nickname, String password, String sessionId, String clientIp); + LoginResponse login(String nickname, String password, String clientIp); } diff --git a/src/main/java/clap/server/application/port/inbound/domain/LogService.java b/src/main/java/clap/server/application/port/inbound/domain/LogService.java index 206ed552..05077cd9 100644 --- a/src/main/java/clap/server/application/port/inbound/domain/LogService.java +++ b/src/main/java/clap/server/application/port/inbound/domain/LogService.java @@ -1,10 +1,15 @@ package clap.server.application.port.inbound.domain; import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus; +import clap.server.application.port.outbound.auth.LoadLoginLogPort; import clap.server.application.port.outbound.log.CommandLogPort; +import clap.server.common.utils.ClientIpParseUtil; +import clap.server.domain.model.auth.LoginLog; import clap.server.domain.model.log.AnonymousLog; import clap.server.domain.model.log.MemberLog; import clap.server.domain.model.member.Member; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -16,6 +21,8 @@ public class LogService { private final CommandLogPort commandLogPort; private final MemberService memberService; + private final LoadLoginLogPort loadLoginLogPort; + private final ObjectMapper objectMapper; public void createAnonymousLog(HttpServletRequest request, int statusCode, String customCode, LogStatus logStatus, Object responseBody, String requestBody, String nickName) { AnonymousLog anonymousLog = AnonymousLog.createAnonymousLog(request, statusCode,customCode, logStatus, responseBody, requestBody, nickName); @@ -27,4 +34,11 @@ public void createMemberLog(HttpServletRequest request, int statusCode, String c MemberLog memberLog = MemberLog.createMemberLog(request, statusCode, customCode, logStatus, responseBody, requestBody, member); commandLogPort.saveMemberLog(memberLog); } + + public void createLoginFailedLog(HttpServletRequest request, int statusCode, String customCode, LogStatus logStatus, String requestBody, String nickName) throws JsonProcessingException { + LoginLog loginLog = loadLoginLogPort.findByClientIp(ClientIpParseUtil.getClientIp(request)).orElse(null); + String responseBody = loginLog != null ? loginLog.toSummaryString() : null; + AnonymousLog anonymousLog = AnonymousLog.createAnonymousLog(request, statusCode,customCode, logStatus, responseBody, requestBody, nickName); + commandLogPort.saveAnonymousLog(anonymousLog); + } } diff --git a/src/main/java/clap/server/application/port/inbound/domain/LoginDomainService.java b/src/main/java/clap/server/application/port/inbound/domain/LoginDomainService.java deleted file mode 100644 index d2362601..00000000 --- a/src/main/java/clap/server/application/port/inbound/domain/LoginDomainService.java +++ /dev/null @@ -1,12 +0,0 @@ -package clap.server.application.port.inbound.domain; - -import org.springframework.stereotype.Service; - -@Service -public class LoginDomainService { - - public int getFailedAttemptCount(String loginNickname) { - //TODO: 로그인 실패 횟수 계산 로직 추가 - return 3; - } -} diff --git a/src/main/java/clap/server/application/port/inbound/task/UpdateTaskLabelUsecase.java b/src/main/java/clap/server/application/port/inbound/task/UpdateTaskLabelUsecase.java index 78a5bfcf..e1a266c7 100644 --- a/src/main/java/clap/server/application/port/inbound/task/UpdateTaskLabelUsecase.java +++ b/src/main/java/clap/server/application/port/inbound/task/UpdateTaskLabelUsecase.java @@ -4,5 +4,5 @@ import clap.server.adapter.inbound.web.dto.task.response.UpdateTaskResponse; public interface UpdateTaskLabelUsecase { - UpdateTaskResponse updateTaskLabel(Long taskId, Long userId, UpdateTaskLabelRequest request); + void updateTaskLabel(Long taskId, Long userId, UpdateTaskLabelRequest request); } diff --git a/src/main/java/clap/server/application/port/inbound/task/UpdateTaskProcessorUsecase.java b/src/main/java/clap/server/application/port/inbound/task/UpdateTaskProcessorUsecase.java index a5cd48b2..9fb8d28c 100644 --- a/src/main/java/clap/server/application/port/inbound/task/UpdateTaskProcessorUsecase.java +++ b/src/main/java/clap/server/application/port/inbound/task/UpdateTaskProcessorUsecase.java @@ -1,8 +1,7 @@ package clap.server.application.port.inbound.task; -import clap.server.adapter.inbound.web.dto.task.response.UpdateTaskResponse; import clap.server.adapter.inbound.web.dto.task.request.UpdateTaskProcessorRequest; public interface UpdateTaskProcessorUsecase { - UpdateTaskResponse updateTaskProcessor(Long taskId, Long userId, UpdateTaskProcessorRequest request); + void updateTaskProcessor(Long taskId, Long userId, UpdateTaskProcessorRequest request); } diff --git a/src/main/java/clap/server/application/port/inbound/task/UpdateTaskStatusUsecase.java b/src/main/java/clap/server/application/port/inbound/task/UpdateTaskStatusUsecase.java index 80c7ff03..b08aa879 100644 --- a/src/main/java/clap/server/application/port/inbound/task/UpdateTaskStatusUsecase.java +++ b/src/main/java/clap/server/application/port/inbound/task/UpdateTaskStatusUsecase.java @@ -4,5 +4,5 @@ import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; public interface UpdateTaskStatusUsecase { - UpdateTaskResponse updateTaskStatus(Long memberId, Long taskId, TaskStatus taskStatus); + void updateTaskStatus(Long memberId, Long taskId, TaskStatus taskStatus); } diff --git a/src/main/java/clap/server/application/port/inbound/task/UpdateTaskUsecase.java b/src/main/java/clap/server/application/port/inbound/task/UpdateTaskUsecase.java index fb78cba6..106c6e58 100644 --- a/src/main/java/clap/server/application/port/inbound/task/UpdateTaskUsecase.java +++ b/src/main/java/clap/server/application/port/inbound/task/UpdateTaskUsecase.java @@ -8,5 +8,5 @@ import java.util.List; public interface UpdateTaskUsecase { - UpdateTaskResponse updateTask(Long memberId, Long taskId, UpdateTaskRequest updateTaskRequest, List files); + void updateTask(Long memberId, Long taskId, UpdateTaskRequest updateTaskRequest, List files); } diff --git a/src/main/java/clap/server/application/port/outbound/auth/CommandLoginLogPort.java b/src/main/java/clap/server/application/port/outbound/auth/CommandLoginLogPort.java index fe6547c9..5101078d 100644 --- a/src/main/java/clap/server/application/port/outbound/auth/CommandLoginLogPort.java +++ b/src/main/java/clap/server/application/port/outbound/auth/CommandLoginLogPort.java @@ -5,5 +5,5 @@ public interface CommandLoginLogPort { void save(LoginLog loginLog); - void deleteById(String sessionId); + void deleteById(String clientIp); } diff --git a/src/main/java/clap/server/application/port/outbound/auth/LoadLoginLogPort.java b/src/main/java/clap/server/application/port/outbound/auth/LoadLoginLogPort.java index 0a02f995..93bb4b59 100644 --- a/src/main/java/clap/server/application/port/outbound/auth/LoadLoginLogPort.java +++ b/src/main/java/clap/server/application/port/outbound/auth/LoadLoginLogPort.java @@ -5,5 +5,5 @@ import java.util.Optional; public interface LoadLoginLogPort { - Optional findBySessionId(String sessionId); + Optional findByClientIp(String clientIp); } diff --git a/src/main/java/clap/server/application/service/auth/AuthService.java b/src/main/java/clap/server/application/service/auth/AuthService.java index 7b26155e..58b6f83b 100644 --- a/src/main/java/clap/server/application/service/auth/AuthService.java +++ b/src/main/java/clap/server/application/service/auth/AuthService.java @@ -34,10 +34,10 @@ class AuthService implements LoginUsecase, LogoutUsecase { @Override - public LoginResponse login(String nickname, String password, String sessionId, String clientIp) { - Member member = getMember(nickname, sessionId, clientIp); + public LoginResponse login(String nickname, String password, String clientIp) { + Member member = getMember(nickname,clientIp); - validatePassword(password, member.getPassword(), sessionId, nickname, clientIp); + validatePassword(password, member.getPassword(), nickname, clientIp); if (member.getStatus().equals(MemberStatus.APPROVAL_REQUEST)) { String temporaryToken = manageTokenService.issueTemporaryToken(member.getMemberId()); @@ -46,7 +46,7 @@ public LoginResponse login(String nickname, String password, String sessionId, S CustomJwts jwtTokens = manageTokenService.issueTokens(member); refreshTokenService.saveRefreshToken(manageTokenService.issueRefreshToken(member.getMemberId())); - loginAttemptService.resetFailedAttempts(sessionId); + loginAttemptService.resetFailedAttempts(clientIp); return AuthResponseMapper.toLoginResponse(jwtTokens.accessToken(), jwtTokens.refreshToken(), member); } @@ -68,17 +68,17 @@ private void deleteAccessToken(Long memberId, String accessToken) { forbiddenTokenPort.save(forbiddenToken); } - private Member getMember(String inputNickname, String sessionId, String clientIp) { + private Member getMember(String inputNickname, String clientIp) { return loadMemberPort.findByNickname(inputNickname).orElseThrow(() -> { - loginAttemptService.recordFailedAttempt(sessionId, clientIp, inputNickname); + loginAttemptService.recordFailedAttempt(clientIp, inputNickname); return new AuthException(AuthErrorCode.LOGIN_REQUEST_FAILED); }); } - private void validatePassword(String inputPassword, String encodedPassword, String sessionId, String inputNickname, String clientIp) { + private void validatePassword(String inputPassword, String encodedPassword, String inputNickname, String clientIp) { if (!passwordEncoder.matches(inputPassword, encodedPassword)) { - loginAttemptService.recordFailedAttempt(sessionId, clientIp, inputNickname); + loginAttemptService.recordFailedAttempt(clientIp, inputNickname); throw new AuthException(AuthErrorCode.LOGIN_REQUEST_FAILED); } } diff --git a/src/main/java/clap/server/application/service/auth/LoginAttemptService.java b/src/main/java/clap/server/application/service/auth/LoginAttemptService.java index fc8be813..a205ae9d 100644 --- a/src/main/java/clap/server/application/service/auth/LoginAttemptService.java +++ b/src/main/java/clap/server/application/service/auth/LoginAttemptService.java @@ -6,25 +6,25 @@ import clap.server.exception.AuthException; import clap.server.exception.code.AuthErrorCode; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; @RequiredArgsConstructor @Component -@Slf4j +@Transactional public class LoginAttemptService { private final LoadLoginLogPort loadLoginLogPort; private final CommandLoginLogPort commandLoginLogPort; private static final int MAX_FAILED_ATTEMPTS = 5; private static final long LOCK_TIME_DURATION = 30 * 60 * 1000; // 30분 (밀리초) - public void recordFailedAttempt(String sessionId, String clientIp, String attemptNickname) { - LoginLog loginLog = loadLoginLogPort.findBySessionId(sessionId).orElse(null); + public void recordFailedAttempt(String clientIp, String attemptNickname) { + LoginLog loginLog = loadLoginLogPort.findByClientIp(clientIp).orElse(null); if (loginLog == null) { - loginLog = LoginLog.createLoginLog(sessionId, clientIp, attemptNickname); + loginLog = LoginLog.createLoginLog(clientIp, attemptNickname); } else { int attemptCount = loginLog.recordFailedAttempt(); if (attemptCount >= MAX_FAILED_ATTEMPTS) { @@ -36,8 +36,8 @@ public void recordFailedAttempt(String sessionId, String clientIp, String attemp commandLoginLogPort.save(loginLog); } - public void checkAccountIsLocked(String sessionId) { - LoginLog loginLog = loadLoginLogPort.findBySessionId(sessionId).orElse(null); + public void checkAccountIsLocked(String clientIp) { + LoginLog loginLog = loadLoginLogPort.findByClientIp(clientIp).orElse(null); if (loginLog == null) { return; } @@ -51,12 +51,12 @@ public void checkAccountIsLocked(String sessionId) { if (minutesSinceLastAttemptInMillis <= LOCK_TIME_DURATION) { throw new AuthException(AuthErrorCode.ACCOUNT_IS_LOCKED); } - commandLoginLogPort.deleteById(sessionId); + else commandLoginLogPort.deleteById(clientIp); } } - public void resetFailedAttempts(String sessionId) { - commandLoginLogPort.deleteById(sessionId); + public void resetFailedAttempts(String clientIp) { + commandLoginLogPort.deleteById(clientIp); } } diff --git a/src/main/java/clap/server/application/service/log/FindApiLogsService.java b/src/main/java/clap/server/application/service/log/FindApiLogsService.java index 1fdc0f0a..6c2c65db 100644 --- a/src/main/java/clap/server/application/service/log/FindApiLogsService.java +++ b/src/main/java/clap/server/application/service/log/FindApiLogsService.java @@ -5,7 +5,6 @@ import clap.server.adapter.inbound.web.dto.log.request.FilterLogRequest; import clap.server.adapter.inbound.web.dto.log.response.MemberLogResponse; import clap.server.application.mapper.LogMapper; -import clap.server.application.port.inbound.domain.LoginDomainService; import clap.server.application.port.inbound.log.FindApiLogsUsecase; import clap.server.application.port.outbound.log.LoadLogPort; import clap.server.common.annotation.architecture.ApplicationService; @@ -20,17 +19,12 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class FindApiLogsService implements FindApiLogsUsecase { - - private final LoginDomainService loginDomainService; private final LoadLogPort loadLogPort; @Override public PageResponse filterAnonymousLogs(FilterLogRequest anonymousLogRequest, Pageable pageable, String sortDirection) { Page anonymousLogs = loadLogPort.filterAnonymousLogs(anonymousLogRequest, pageable, sortDirection); - Page anonymousLogResponses = anonymousLogs.map(anonymousLog -> { - int failedAttempts = loginDomainService.getFailedAttemptCount(anonymousLog.getLoginNickname()); - return LogMapper.toAnonymousLogResponse(anonymousLog, failedAttempts); - }); + Page anonymousLogResponses = anonymousLogs.map(anonymousLog -> LogMapper.toAnonymousLogResponse(anonymousLog)); return PageResponse.from(anonymousLogResponses); } diff --git a/src/main/java/clap/server/application/service/task/UpdateTaskBoardService.java b/src/main/java/clap/server/application/service/task/UpdateTaskBoardService.java index 8cd7a74d..cd08f9e9 100644 --- a/src/main/java/clap/server/application/service/task/UpdateTaskBoardService.java +++ b/src/main/java/clap/server/application/service/task/UpdateTaskBoardService.java @@ -7,11 +7,11 @@ import clap.server.application.port.inbound.task.UpdateTaskBoardUsecase; import clap.server.application.port.inbound.task.UpdateTaskOrderAndStatusUsecase; import clap.server.application.port.outbound.task.LoadTaskPort; -import clap.server.domain.policy.task.TaskOrderCalculationPolicy; -import clap.server.domain.policy.task.ProcessorValidationPolicy; import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Member; import clap.server.domain.model.task.Task; +import clap.server.domain.policy.task.ProcessorValidationPolicy; +import clap.server.domain.policy.task.TaskOrderCalculationPolicy; import clap.server.domain.policy.task.TaskPolicyConstants; import clap.server.exception.ApplicationException; import clap.server.exception.code.TaskErrorCode; diff --git a/src/main/java/clap/server/application/service/task/UpdateTaskService.java b/src/main/java/clap/server/application/service/task/UpdateTaskService.java index 82029f1d..70bee596 100644 --- a/src/main/java/clap/server/application/service/task/UpdateTaskService.java +++ b/src/main/java/clap/server/application/service/task/UpdateTaskService.java @@ -36,6 +36,8 @@ import java.util.List; +import static clap.server.domain.policy.task.TaskPolicyConstants.TASK_UPDATABLE_STATUS; + @ApplicationService @RequiredArgsConstructor @@ -54,7 +56,7 @@ public class UpdateTaskService implements UpdateTaskUsecase, UpdateTaskStatusUse @Override @Transactional - public UpdateTaskResponse updateTask(Long requesterId, Long taskId, UpdateTaskRequest updateTaskRequest, List files) { + public void updateTask(Long requesterId, Long taskId, UpdateTaskRequest updateTaskRequest, List files) { Member requester = memberService.findActiveMember(requesterId); Category category = categoryService.findById(updateTaskRequest.categoryId()); Task task = taskService.findById(taskId); @@ -65,25 +67,30 @@ public UpdateTaskResponse updateTask(Long requesterId, Long taskId, UpdateTaskRe if (!updateTaskRequest.attachmentsToDelete().isEmpty()) { updateAttachments(updateTaskRequest.attachmentsToDelete(), files, task); } - return TaskResponseMapper.toUpdateTaskResponse(updatedTask); } @Override @Transactional - public UpdateTaskResponse updateTaskStatus(Long memberId, Long taskId, TaskStatus taskStatus) { + public void updateTaskStatus(Long memberId, Long taskId, TaskStatus taskStatus) { memberService.findActiveMember(memberId); memberService.findReviewer(memberId); Task task = taskService.findById(taskId); - task.updateTaskStatus(taskStatus); - Task updateTask = taskService.upsert(task); - publishNotification(updateTask, NotificationType.STATUS_SWITCHED, String.valueOf(updateTask.getTaskStatus())); - return TaskResponseMapper.toUpdateTaskResponse(updateTask); + if(!TASK_UPDATABLE_STATUS.contains(taskStatus)){ + throw new ApplicationException(TaskErrorCode.TASK_STATUS_NOT_ALLOWED); + } + + if(!task.getTaskStatus().equals(taskStatus)){ + task.updateTaskStatus(taskStatus); + Task updateTask = taskService.upsert(task); + + publishNotification(updateTask, NotificationType.STATUS_SWITCHED, String.valueOf(updateTask.getTaskStatus())); + } } @Transactional @Override - public UpdateTaskResponse updateTaskProcessor(Long taskId, Long userId, UpdateTaskProcessorRequest request) { + public void updateTaskProcessor(Long taskId, Long userId, UpdateTaskProcessorRequest request) { memberService.findActiveMember(userId); memberService.findReviewer(userId); Member processor = memberService.findById(request.processorId()); @@ -93,12 +100,11 @@ public UpdateTaskResponse updateTaskProcessor(Long taskId, Long userId, UpdateTa Task updateTask = taskService.upsert(task); publishNotification(updateTask, NotificationType.PROCESSOR_CHANGED, updateTask.getProcessor().getNickname()); - return TaskResponseMapper.toUpdateTaskResponse(updateTask); } @Transactional @Override - public UpdateTaskResponse updateTaskLabel(Long taskId, Long userId, UpdateTaskLabelRequest request) { + public void updateTaskLabel(Long taskId, Long userId, UpdateTaskLabelRequest request) { memberService.findActiveMember(userId); memberService.findReviewer(userId); Task task = taskService.findById(taskId); @@ -106,7 +112,6 @@ public UpdateTaskResponse updateTaskLabel(Long taskId, Long userId, UpdateTaskLa task.updateLabel(label); Task updatetask = taskService.upsert(task); - return TaskResponseMapper.toUpdateTaskResponse(updatetask); } private void updateAttachments(List attachmentIdsToDelete, List files, Task task) { diff --git a/src/main/java/clap/server/config/aop/LoggingAspect.java b/src/main/java/clap/server/config/aop/LoggingAspect.java index 75eea464..760e08fa 100644 --- a/src/main/java/clap/server/config/aop/LoggingAspect.java +++ b/src/main/java/clap/server/config/aop/LoggingAspect.java @@ -6,6 +6,7 @@ import clap.server.application.port.inbound.domain.LogService; import clap.server.common.annotation.log.LogType; import clap.server.exception.BaseException; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; @@ -35,7 +36,6 @@ @RequiredArgsConstructor public class LoggingAspect { private final ObjectMapper objectMapper; - private final HandlerExceptionResolver handlerExceptionResolver; private final LogService logService; @Pointcut("execution(* clap.server.adapter.inbound.web..*Controller.*(..))") @@ -59,6 +59,7 @@ public Object logApiRequests(ProceedingJoinPoint joinPoint) throws Throwable { LogStatus logStatus = getLogType((MethodSignature) joinPoint.getSignature()); int statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR; String customCode = null; + if (capturedException != null) { if (capturedException instanceof BaseException e) { statusCode = e.getCode().getHttpStatus().value(); @@ -70,7 +71,8 @@ public Object logApiRequests(ProceedingJoinPoint joinPoint) throws Throwable { if (logStatus != null) { if (LogStatus.LOGIN.equals(logStatus)) { - logService.createAnonymousLog(request, statusCode, customCode, logStatus, result, getRequestBody(request), getNicknameFromRequestBody(request)); + handleLoginLog(statusCode, request, customCode, logStatus, result); + } else { if (!isUserAuthenticated()) { log.error("로그인 시도 로그를 기록할 수 없음"); @@ -86,6 +88,14 @@ public Object logApiRequests(ProceedingJoinPoint joinPoint) throws Throwable { return result; } + private void handleLoginLog(int statusCode, HttpServletRequest request, String customCode, LogStatus logStatus, Object result) throws JsonProcessingException { + if (statusCode == HttpStatus.SC_OK) { + logService.createAnonymousLog(request, statusCode, customCode, logStatus, result, getRequestBody(request), getNicknameFromRequestBody(request)); + } else { + logService.createLoginFailedLog(request, statusCode, customCode, logStatus, getRequestBody(request), getNicknameFromRequestBody(request)); + } + } + private LogStatus getLogType(MethodSignature methodSignature) { if (methodSignature.getMethod().isAnnotationPresent(LogType.class)) { return methodSignature.getMethod().getAnnotation(LogType.class).value(); diff --git a/src/main/java/clap/server/domain/model/auth/LoginLog.java b/src/main/java/clap/server/domain/model/auth/LoginLog.java index cc454f20..11f89c19 100644 --- a/src/main/java/clap/server/domain/model/auth/LoginLog.java +++ b/src/main/java/clap/server/domain/model/auth/LoginLog.java @@ -11,41 +11,45 @@ @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) public class LoginLog { - private String sessionId; private String clientIp; private String attemptNickname; private LocalDateTime lastAttemptAt; - private int attemptCount; + private int failedCount; private boolean isLocked; @Builder - private LoginLog(String sessionId, String clientIp, String attemptNickname, LocalDateTime lastAttemptAt, - int attemptCount, boolean isLocked) { - this.sessionId = sessionId; + private LoginLog(String clientIp, String attemptNickname, LocalDateTime lastAttemptAt, + int failedCount, boolean isLocked) { this.clientIp = clientIp; this.attemptNickname = attemptNickname; this.lastAttemptAt = lastAttemptAt; - this.attemptCount = attemptCount; + this.failedCount = failedCount; this.isLocked = isLocked; } - public static LoginLog createLoginLog(String sessionId, String clientIp, String attemptNickname) { + public static LoginLog createLoginLog(String clientIp, String attemptNickname) { return LoginLog.builder() - .sessionId(sessionId) .clientIp(clientIp) .attemptNickname(attemptNickname) .lastAttemptAt(LocalDateTime.now()) - .attemptCount(1) + .failedCount(1) .isLocked(false) .build(); } public int recordFailedAttempt() { - this.attemptCount++; - return this.attemptCount; + this.failedCount++; + return this.failedCount; } public void setLocked(boolean locked) { isLocked = locked; } + + public String toSummaryString() { + return "{" + + ", failedCount=" + failedCount + + ", isLocked=" + isLocked + + '}'; + } } diff --git a/src/main/java/clap/server/exception/code/AuthErrorCode.java b/src/main/java/clap/server/exception/code/AuthErrorCode.java index c223d41d..acb9b5a3 100644 --- a/src/main/java/clap/server/exception/code/AuthErrorCode.java +++ b/src/main/java/clap/server/exception/code/AuthErrorCode.java @@ -21,9 +21,9 @@ public enum AuthErrorCode implements BaseErrorCode { FORBIDDEN_ACCESS_TOKEN(HttpStatus.FORBIDDEN, "AUTH_012","해당 토큰에는 엑세스 권한이 없습니다."), INVALID_TOKEN(HttpStatus.UNAUTHORIZED,"AUTH_013", "유효하지 않은 토큰입니다."), REFRESH_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH_014", "리프레시 토큰을 찾을 수 없습니다."), - REFRESH_TOKEN_MISMATCHED(HttpStatus.UNAUTHORIZED, "AUTH_014", "리프레시 토큰이 일치하지 않습니다"), ACCOUNT_IS_LOCKED(HttpStatus.UNAUTHORIZED, "AUTH_015", "접근할 수 없는 계정입니다."), - LOGIN_REQUEST_FAILED(HttpStatus.UNAUTHORIZED, "AUTH_016", "로그인에 실패하였습니다.") + LOGIN_REQUEST_FAILED(HttpStatus.UNAUTHORIZED, "AUTH_016", "로그인에 실패하였습니다."), + REFRESH_TOKEN_MISMATCHED(HttpStatus.UNAUTHORIZED, "AUTH_017", "리프레시 토큰이 일치하지 않습니다"), ; private final HttpStatus httpStatus; diff --git a/src/main/java/clap/server/exception/code/TaskErrorCode.java b/src/main/java/clap/server/exception/code/TaskErrorCode.java index c7976c2b..f5c551af 100644 --- a/src/main/java/clap/server/exception/code/TaskErrorCode.java +++ b/src/main/java/clap/server/exception/code/TaskErrorCode.java @@ -17,7 +17,7 @@ public enum TaskErrorCode implements BaseErrorCode { INVALID_TASK_STATUS_TRANSITION(HttpStatus.BAD_REQUEST, "TASK_008", "유효하지 않은 작업 상태 전환입니다."), NOT_A_REVIEWER(HttpStatus.FORBIDDEN, "TASK_009", "작업 승인 및 수정 권한이 없습니다."), NOT_A_REQUESTER(HttpStatus.FORBIDDEN, "TASK_009", "작업 수정 권한이 없습니다."), - TASK_STATUS_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "TASK_010", "변경할 수 없는 작업 상태입니다.") + TASK_STATUS_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "TASK_010", "변경할 수 없는 작업 상태입니다. 다른 API를 사용해주세요") ; private final HttpStatus httpStatus;