Skip to content

Commit 47fc6c6

Browse files
authored
Merge pull request #296 from TaskFlow-CLAP/CLAP-241
CLAP-241 로그인 시도 감지 필터 로직 수정 및 로그인 실패 로깅 추가
2 parents ecb1dc6 + f6ad78e commit 47fc6c6

26 files changed

+120
-106
lines changed

src/main/java/clap/server/adapter/inbound/security/filter/LoginAttemptFilter.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
import clap.server.application.service.auth.LoginAttemptService;
44
import clap.server.exception.AuthException;
5-
import clap.server.exception.code.GlobalErrorCode;
65
import jakarta.servlet.FilterChain;
76
import jakarta.servlet.ServletException;
87
import jakarta.servlet.http.HttpServletRequest;
98
import jakarta.servlet.http.HttpServletResponse;
109
import lombok.RequiredArgsConstructor;
1110
import lombok.extern.slf4j.Slf4j;
11+
import org.springframework.http.HttpStatus;
1212
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1313
import org.springframework.security.core.context.SecurityContextHolder;
1414
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@@ -18,7 +18,7 @@
1818
import java.util.ArrayList;
1919

2020
import static clap.server.adapter.inbound.security.WebSecurityUrl.LOGIN_ENDPOINT;
21-
import static clap.server.common.constants.AuthConstants.SESSION_ID;
21+
import static clap.server.common.utils.ClientIpParseUtil.getClientIp;
2222

2323

2424
@RequiredArgsConstructor
@@ -30,13 +30,19 @@ public class LoginAttemptFilter extends OncePerRequestFilter {
3030
@Override
3131
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
3232
throws ServletException, IOException {
33-
String sessionId = request.getHeader(SESSION_ID.getValue().toLowerCase());
33+
try {
34+
if (request.getRequestURI().equals(LOGIN_ENDPOINT)) {
35+
String clientIp = getClientIp(request);
36+
37+
loginAttemptService.checkAccountIsLocked(clientIp);
3438

35-
if (request.getRequestURI().equals(LOGIN_ENDPOINT)) {
36-
if (sessionId == null) {
37-
throw new AuthException(GlobalErrorCode.BAD_REQUEST);
3839
}
39-
loginAttemptService.checkAccountIsLocked(sessionId);
40+
} catch (AuthException e) {
41+
log.warn("Authentication failed for IP: {}. Error: {}", getClientIp(request), e.getMessage());
42+
response.setStatus(HttpStatus.UNAUTHORIZED.value());
43+
response.getWriter().write(e.getErrorCode().getCustomCode());
44+
log.info("Sent error response: {}", e.getErrorCode().getCustomCode());
45+
return;
4046
}
4147

4248
UsernamePasswordAuthenticationToken authenticationToken =

src/main/java/clap/server/adapter/inbound/security/handler/JwtAuthenticationEntryPoint.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ public void commence(
2323
) throws IOException, ServletException {
2424
AuthErrorCode errorCode = AuthErrorCode.UNAUTHORIZED;
2525

26-
response.setContentType("application/json;charset=UTF-8");
2726
response.setStatus(errorCode.getHttpStatus().value());
2827
response.getWriter().write(errorCode.getCustomCode());
2928
}

src/main/java/clap/server/adapter/inbound/web/auth/AuthController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ public class AuthController {
3232
@LogType(LogStatus.LOGIN)
3333
@Operation(summary = "로그인 API")
3434
@PostMapping("/login")
35-
public ResponseEntity<LoginResponse> login(@RequestHeader(name = "sessionId") String sessionId,
35+
public ResponseEntity<LoginResponse> login(
3636
@RequestBody LoginRequest request,
3737
HttpServletRequest httpRequest) {
3838
String clientIp = getClientIp(httpRequest);
39-
LoginResponse response = loginUsecase.login(request.nickname(), request.password(), sessionId, clientIp);
39+
LoginResponse response = loginUsecase.login(request.nickname(), request.password(), clientIp);
4040
return ResponseEntity.ok(response);
4141
}
4242

src/main/java/clap/server/adapter/inbound/web/dto/log/response/AnonymousLogResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ public record AnonymousLogResponse(
1616
String clientIp,
1717
@NotBlank
1818
Integer statusCode,
19-
int failedAttempts
19+
String failedAttempts
2020
) {
2121
}

src/main/java/clap/server/adapter/inbound/web/task/ChangeTaskController.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import clap.server.adapter.inbound.web.dto.task.request.UpdateTaskLabelRequest;
66
import clap.server.adapter.inbound.web.dto.task.request.UpdateTaskProcessorRequest;
77
import clap.server.adapter.inbound.web.dto.task.response.ApprovalTaskResponse;
8-
import clap.server.adapter.inbound.web.dto.task.response.UpdateTaskResponse;
98
import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus;
109
import clap.server.application.port.inbound.task.ApprovalTaskUsecase;
1110
import clap.server.application.port.inbound.task.UpdateTaskLabelUsecase;
@@ -40,34 +39,33 @@ public class ChangeTaskController {
4039
@Operation(summary = "작업 상태 변경")
4140
@Secured("ROLE_MANAGER")
4241
@PatchMapping("/{taskId}/status")
43-
public ResponseEntity<UpdateTaskResponse> updateTaskState(
42+
public void updateTaskState(
4443
@PathVariable @NotNull Long taskId,
4544
@AuthenticationPrincipal SecurityUserDetails userInfo,
4645
@Parameter(description = "변경하고 싶은 작업 상태",
4746
schema = @Schema(allowableValues = {"IN_PROGRESS", "PENDING_COMPLETED", "COMPLETED"}))
4847
@RequestBody TaskStatus taskStatus) {
49-
50-
return ResponseEntity.ok(updateTaskStatusUsecase.updateTaskStatus(userInfo.getUserId(), taskId, taskStatus));
48+
updateTaskStatusUsecase.updateTaskStatus(userInfo.getUserId(), taskId, taskStatus);
5149
}
5250

5351
@Operation(summary = "작업 처리자 변경")
5452
@Secured({"ROLE_MANAGER"})
5553
@PatchMapping("/{taskId}/processor")
56-
public ResponseEntity<UpdateTaskResponse> updateTaskProcessor(
54+
public void updateTaskProcessor(
5755
@PathVariable Long taskId,
5856
@AuthenticationPrincipal SecurityUserDetails userInfo,
5957
@Valid @RequestBody UpdateTaskProcessorRequest updateTaskProcessorRequest) {
60-
return ResponseEntity.ok(updateTaskProcessorUsecase.updateTaskProcessor(taskId, userInfo.getUserId(), updateTaskProcessorRequest));
58+
updateTaskProcessorUsecase.updateTaskProcessor(taskId, userInfo.getUserId(), updateTaskProcessorRequest);
6159
}
6260

6361
@Operation(summary = "작업 구분 변경")
6462
@Secured({"ROLE_MANAGER"})
6563
@PatchMapping("/{taskId}/label")
66-
public ResponseEntity<UpdateTaskResponse> updateTaskLabel(
64+
public void updateTaskLabel(
6765
@PathVariable Long taskId,
6866
@AuthenticationPrincipal SecurityUserDetails userInfo,
6967
@Valid @RequestBody UpdateTaskLabelRequest updateTaskLabelRequest) {
70-
return ResponseEntity.ok(updateTaskLabelUsecase.updateTaskLabel(taskId, userInfo.getUserId(), updateTaskLabelRequest));
68+
updateTaskLabelUsecase.updateTaskLabel(taskId, userInfo.getUserId(), updateTaskLabelRequest);
7169
}
7270

7371
@Operation(summary = "작업 승인")

src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ public ResponseEntity<CreateTaskResponse> createTask(
4646
@Operation(summary = "작업 수정")
4747
@PatchMapping(value = "/{taskId}", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
4848
@Secured({"ROLE_MANAGER", "ROLE_USER"})
49-
public ResponseEntity<UpdateTaskResponse> updateTask(
49+
public void updateTask(
5050
@PathVariable @NotNull Long taskId,
5151
@RequestPart(name = "taskInfo") @Valid UpdateTaskRequest updateTaskRequest,
5252
@RequestPart(name = "attachment", required = false) List<MultipartFile> attachments,
5353
@AuthenticationPrincipal SecurityUserDetails userInfo){
54-
return ResponseEntity.ok(updateTaskUsecase.updateTask(userInfo.getUserId(), taskId, updateTaskRequest, attachments));
54+
updateTaskUsecase.updateTask(userInfo.getUserId(), taskId, updateTaskRequest, attachments);
5555
}
5656
}

src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogAdapter.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ public void save(LoginLog loginLog) {
2121
loginLogRepository.save(loginLogEntity);
2222
}
2323

24-
public void deleteById(String sessionId) {
25-
loginLogRepository.deleteById(sessionId);
24+
public void deleteById(String clientIp) {
25+
loginLogRepository.deleteById(clientIp);
2626
}
2727

28-
public Optional<LoginLog> findBySessionId(String sessionId) {
29-
return loginLogRepository.findById(sessionId).map(loginLogMapper::toDomain);
28+
public Optional<LoginLog> findByClientIp(String clientIp) {
29+
return loginLogRepository.findById(clientIp).map(loginLogMapper::toDomain);
3030
}
3131
}

src/main/java/clap/server/adapter/outbound/infrastructure/redis/log/LoginLogEntity.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,26 @@
1010
import lombok.ToString;
1111
import org.springframework.data.annotation.Id;
1212
import org.springframework.data.redis.core.RedisHash;
13-
import org.springframework.data.redis.core.index.Indexed;
1413

1514
import java.time.LocalDateTime;
1615

1716
@Getter
1817
@RedisHash("loginLog")
1918
@Builder
20-
@ToString(of = {"sessionId", "clientIp", "attemptNickname", "lastAttemptAt", "attemptCount", "isLocked"})
21-
@EqualsAndHashCode(of = {"sessionId"})
19+
@ToString(of = {"clientIp", "attemptNickname", "lastAttemptAt", "failedCount", "isLocked"})
20+
@EqualsAndHashCode(of = {"clientIp"})
2221
public class LoginLogEntity {
2322
@Id
24-
private String sessionId;
25-
2623
private String clientIp;
2724

2825
private String attemptNickname;
2926

30-
@JsonSerialize(using = ToStringSerializer.class) // 직렬화 방식을 설정
27+
@JsonSerialize(using = ToStringSerializer.class)
3128
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
3229
@Builder.Default
3330
private LocalDateTime lastAttemptAt = LocalDateTime.now();
3431

35-
private int attemptCount;
32+
private int failedCount;
3633

3734
private boolean isLocked;
3835
}

src/main/java/clap/server/application/mapper/LogMapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
import clap.server.domain.model.log.MemberLog;
77

88
public class LogMapper {
9-
public static AnonymousLogResponse toAnonymousLogResponse(AnonymousLog anonymousLog, int failedAttempts) {
9+
public static AnonymousLogResponse toAnonymousLogResponse(AnonymousLog anonymousLog) {
1010
return new AnonymousLogResponse(
1111
anonymousLog.getLogId(),
1212
anonymousLog.getLogStatus(),
1313
anonymousLog.getRequestAt(),
1414
anonymousLog.getLoginNickname(),
1515
anonymousLog.getClientIp(),
1616
anonymousLog.getStatusCode(),
17-
failedAttempts
17+
anonymousLog.getResponseBody()
1818
);
1919
}
2020
public static MemberLogResponse toMemberLogResponse(MemberLog memberLog) {

src/main/java/clap/server/application/port/inbound/auth/LoginUsecase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
import clap.server.adapter.inbound.web.dto.auth.response.LoginResponse;
44

55
public interface LoginUsecase {
6-
LoginResponse login(String nickname, String password, String sessionId, String clientIp);
6+
LoginResponse login(String nickname, String password, String clientIp);
77
}

0 commit comments

Comments
 (0)