Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ public class AuthController {
@LogType(LogStatus.LOGIN)
@Operation(summary = "로그인 API")
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@RequestHeader(name = "sessionId") String sessionId,
public ResponseEntity<LoginResponse> 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public record AnonymousLogResponse(
String clientIp,
@NotBlank
Integer statusCode,
int failedAttempts
String failedAttempts
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -40,34 +39,33 @@ public class ChangeTaskController {
@Operation(summary = "작업 상태 변경")
@Secured("ROLE_MANAGER")
@PatchMapping("/{taskId}/status")
public ResponseEntity<UpdateTaskResponse> 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<UpdateTaskResponse> 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<UpdateTaskResponse> 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 = "작업 승인")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ public ResponseEntity<CreateTaskResponse> createTask(
@Operation(summary = "작업 수정")
@PatchMapping(value = "/{taskId}", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
@Secured({"ROLE_MANAGER", "ROLE_USER"})
public ResponseEntity<UpdateTaskResponse> updateTask(
public void updateTask(
@PathVariable @NotNull Long taskId,
@RequestPart(name = "taskInfo") @Valid UpdateTaskRequest updateTaskRequest,
@RequestPart(name = "attachment", required = false) List<MultipartFile> attachments,
@AuthenticationPrincipal SecurityUserDetails userInfo){
return ResponseEntity.ok(updateTaskUsecase.updateTask(userInfo.getUserId(), taskId, updateTaskRequest, attachments));
updateTaskUsecase.updateTask(userInfo.getUserId(), taskId, updateTaskRequest, attachments);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<LoginLog> findBySessionId(String sessionId) {
return loginLogRepository.findById(sessionId).map(loginLogMapper::toDomain);
public Optional<LoginLog> findByClientIp(String clientIp) {
return loginLogRepository.findById(clientIp).map(loginLogMapper::toDomain);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
4 changes: 2 additions & 2 deletions src/main/java/clap/server/application/mapper/LogMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
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(),
anonymousLog.getRequestAt(),
anonymousLog.getLoginNickname(),
anonymousLog.getClientIp(),
anonymousLog.getStatusCode(),
failedAttempts
anonymousLog.getResponseBody()
);
}
public static MemberLogResponse toMemberLogResponse(MemberLog memberLog) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand All @@ -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);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
import java.util.List;

public interface UpdateTaskUsecase {
UpdateTaskResponse updateTask(Long memberId, Long taskId, UpdateTaskRequest updateTaskRequest, List<MultipartFile> files);
void updateTask(Long memberId, Long taskId, UpdateTaskRequest updateTaskRequest, List<MultipartFile> files);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
public interface CommandLoginLogPort {
void save(LoginLog loginLog);

void deleteById(String sessionId);
void deleteById(String clientIp);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
import java.util.Optional;

public interface LoadLoginLogPort {
Optional<LoginLog> findBySessionId(String sessionId);
Optional<LoginLog> findByClientIp(String clientIp);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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);
}

Expand All @@ -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);
}
}
Expand Down
Loading