Skip to content

Commit

Permalink
refactor: 알림 시스템 이벤트 기반 리팩토링 및 멀티플랫폼 지원 구현 완료 (#615)
Browse files Browse the repository at this point in the history
  • Loading branch information
limehee authored Nov 27, 2024
1 parent 3ca00d5 commit a9ad4e5
Show file tree
Hide file tree
Showing 69 changed files with 1,689 additions and 856 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ tasks.named('test') {
def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile

sourceSets {
main.java.srcDirs += [ querydslDir ]
main.java.srcDirs += [querydslDir]
}

tasks.withType(JavaCompile).configureEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.auth.accountLockInfo.application.port.in.BanMemberUseCase;
Expand All @@ -11,8 +12,8 @@
import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto;
import page.clab.api.external.auth.redisToken.application.port.ExternalManageRedisTokenUseCase;
import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase;
import page.clab.api.global.common.slack.application.SlackService;
import page.clab.api.global.common.slack.domain.SecurityAlertType;
import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent;
import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType;

@Service
@RequiredArgsConstructor
Expand All @@ -22,15 +23,15 @@ public class MemberBanService implements BanMemberUseCase {
private final RegisterAccountLockInfoPort registerAccountLockInfoPort;
private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase;
private final ExternalManageRedisTokenUseCase externalManageRedisTokenUseCase;
private final SlackService slackService;
private final ApplicationEventPublisher eventPublisher;

/**
* 멤버를 영구적으로 차단합니다.
*
* <p>해당 멤버의 계정 잠금 정보를 조회하고, 없으면 새로 생성합니다.
* Redis에 저장된 해당 멤버의 인증 토큰을 삭제하며, Slack에 밴 알림을 전송합니다.</p>
*
* @param request 현재 요청 객체
* @param request 현재 요청 객체
* @param memberId 차단할 멤버의 ID
* @return 저장된 계정 잠금 정보의 ID
*/
Expand Down Expand Up @@ -58,6 +59,8 @@ private AccountLockInfo createAccountLockInfo(String memberId) {

private void sendSlackBanNotification(HttpServletRequest request, String memberId) {
String memberName = externalRetrieveMemberUseCase.getMemberBasicInfoById(memberId).getMemberName();
slackService.sendSecurityAlertNotification(request, SecurityAlertType.MEMBER_BANNED, "ID: " + memberId + ", Name: " + memberName);
String memberBannedMessage = "ID: " + memberId + ", Name: " + memberName;
eventPublisher.publishEvent(
new NotificationEvent(this, SecurityAlertType.MEMBER_BANNED, request, memberBannedMessage));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.auth.accountLockInfo.application.port.in.UnbanMemberUseCase;
Expand All @@ -10,8 +11,8 @@
import page.clab.api.domain.auth.accountLockInfo.domain.AccountLockInfo;
import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto;
import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase;
import page.clab.api.global.common.slack.application.SlackService;
import page.clab.api.global.common.slack.domain.SecurityAlertType;
import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent;
import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType;

@Service
@RequiredArgsConstructor
Expand All @@ -20,15 +21,15 @@ public class MemberUnbanService implements UnbanMemberUseCase {
private final RetrieveAccountLockInfoPort retrieveAccountLockInfoPort;
private final RegisterAccountLockInfoPort registerAccountLockInfoPort;
private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase;
private final SlackService slackService;
private final ApplicationEventPublisher eventPublisher;

/**
* 차단된 멤버를 해제합니다.
*
* <p>해당 멤버의 계정 잠금 정보를 조회하고 해제합니다.
* 해제된 정보는 저장되며, Slack에 해제 알림이 전송됩니다.</p>
*
* @param request 현재 요청 객체
* @param request 현재 요청 객체
* @param memberId 해제할 멤버의 ID
* @return 업데이트된 계정 잠금 정보의 ID
*/
Expand All @@ -55,6 +56,8 @@ private AccountLockInfo createAccountLockInfo(String memberId) {

private void sendSlackUnbanNotification(HttpServletRequest request, String memberId) {
String memberName = externalRetrieveMemberUseCase.getMemberBasicInfoById(memberId).getMemberName();
slackService.sendSecurityAlertNotification(request, SecurityAlertType.MEMBER_UNBANNED, "ID: " + memberId + ", Name: " + memberName);
String memberUnbannedMessage = "ID: " + memberId + ", Name: " + memberName;
eventPublisher.publishEvent(
new NotificationEvent(this, SecurityAlertType.MEMBER_UNBANNED, request, memberUnbannedMessage));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.auth.blacklistIp.application.dto.mapper.BlacklistIpDtoMapper;
Expand All @@ -10,26 +11,25 @@
import page.clab.api.domain.auth.blacklistIp.application.port.out.RegisterBlacklistIpPort;
import page.clab.api.domain.auth.blacklistIp.application.port.out.RetrieveBlacklistIpPort;
import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp;
import page.clab.api.global.common.slack.application.SlackService;
import page.clab.api.global.common.slack.domain.SecurityAlertType;
import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent;
import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType;

@Service
@RequiredArgsConstructor
public class BlacklistIpRegisterService implements RegisterBlacklistIpUseCase {

private final RegisterBlacklistIpPort registerBlacklistIpPort;
private final RetrieveBlacklistIpPort retrieveBlacklistIpPort;
private final SlackService slackService;
private final ApplicationEventPublisher eventPublisher;
private final BlacklistIpDtoMapper mapper;

/**
* 지정된 IP 주소를 블랙리스트에 등록합니다.
*
* <p>해당 IP 주소가 이미 블랙리스트에 존재하는지 확인하고,
* 존재하지 않을 경우 새롭게 등록합니다.
* 새로운 IP가 등록되면 Slack을 통해 보안 알림이 전송됩니다.</p>
* 존재하지 않을 경우 새롭게 등록합니다. 새로운 IP가 등록되면 Slack을 통해 보안 알림이 전송됩니다.</p>
*
* @param request 현재 요청 객체
* @param request 현재 요청 객체
* @param requestDto 블랙리스트에 추가할 IP 주소 정보를 담은 DTO
* @return 기존에 존재하거나 새로 추가된 블랙리스트 IP 주소
*/
Expand All @@ -42,7 +42,12 @@ public String registerBlacklistIp(HttpServletRequest request, BlacklistIpRequest
.orElseGet(() -> {
BlacklistIp blacklistIp = mapper.fromDto(requestDto);
registerBlacklistIpPort.save(blacklistIp);
slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_ADDED, "Added IP: " + ipAddress);

String blacklistAddedMessage = "Added IP: " + ipAddress;
eventPublisher.publishEvent(
new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_ADDED, request,
blacklistAddedMessage));

return ipAddress;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,31 @@

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.auth.blacklistIp.application.port.in.RemoveBlacklistIpUseCase;
import page.clab.api.domain.auth.blacklistIp.application.port.out.RemoveBlacklistIpPort;
import page.clab.api.domain.auth.blacklistIp.application.port.out.RetrieveBlacklistIpPort;
import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp;
import page.clab.api.global.common.slack.application.SlackService;
import page.clab.api.global.common.slack.domain.SecurityAlertType;
import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent;
import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType;

@Service
@RequiredArgsConstructor
public class BlacklistIpRemoveService implements RemoveBlacklistIpUseCase {

private final RetrieveBlacklistIpPort retrieveBlacklistIpPort;
private final RemoveBlacklistIpPort removeBlacklistIpPort;
private final SlackService slackService;
private final ApplicationEventPublisher eventPublisher;

/**
* 지정된 IP 주소를 블랙리스트에서 제거합니다.
*
* <p>블랙리스트에 등록된 IP 주소 정보를 조회하고 해당 정보를 삭제합니다.
* 삭제가 완료되면 Slack을 통해 보안 알림이 전송됩니다.</p>
*
* @param request 현재 요청 객체
* @param request 현재 요청 객체
* @param ipAddress 제거할 블랙리스트 IP 주소
* @return 삭제된 블랙리스트 IP 주소
*/
Expand All @@ -34,7 +35,12 @@ public class BlacklistIpRemoveService implements RemoveBlacklistIpUseCase {
public String removeBlacklistIp(HttpServletRequest request, String ipAddress) {
BlacklistIp blacklistIp = retrieveBlacklistIpPort.getByIpAddress(ipAddress);
removeBlacklistIpPort.delete(blacklistIp);
slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_REMOVED, "Deleted IP: " + ipAddress);

String blacklistRemovedMessage = "Deleted IP: " + ipAddress;
eventPublisher.publishEvent(
new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_REMOVED, request,
blacklistRemovedMessage));

return blacklistIp.getIpAddress();
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package page.clab.api.domain.auth.blacklistIp.application.service;

import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.auth.blacklistIp.application.port.in.ResetBlacklistIpsUseCase;
import page.clab.api.domain.auth.blacklistIp.application.port.out.RemoveBlacklistIpPort;
import page.clab.api.domain.auth.blacklistIp.application.port.out.RetrieveBlacklistIpPort;
import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp;
import page.clab.api.global.common.slack.application.SlackService;
import page.clab.api.global.common.slack.domain.SecurityAlertType;

import java.util.List;
import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent;
import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType;

@Service
@RequiredArgsConstructor
public class BlacklistIpResetService implements ResetBlacklistIpsUseCase {

private final RetrieveBlacklistIpPort retrieveBlacklistIpPort;
private final RemoveBlacklistIpPort removeBlacklistIpPort;
private final SlackService slackService;
private final ApplicationEventPublisher eventPublisher;

/**
* 블랙리스트에 등록된 모든 IP 주소를 초기화합니다.
Expand All @@ -38,7 +38,12 @@ public List<String> resetBlacklistIps(HttpServletRequest request) {
.map(BlacklistIp::getIpAddress)
.toList();
removeBlacklistIpPort.deleteAll();
slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_REMOVED, "Deleted IP: ALL");

String blacklistRemovedMessage = "Deleted IP: ALL";
eventPublisher.publishEvent(
new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_REMOVED, request,
blacklistRemovedMessage));

return blacklistedIps;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package page.clab.api.domain.auth.login.application.service;

import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.auth.accountAccessLog.domain.AccountAccessResult;
Expand All @@ -21,11 +23,10 @@
import page.clab.api.external.auth.redisToken.application.port.ExternalManageRedisTokenUseCase;
import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase;
import page.clab.api.global.auth.jwt.JwtTokenProvider;
import page.clab.api.global.common.slack.application.SlackService;
import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent;
import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType;
import page.clab.api.global.util.HttpReqResUtil;

import java.util.List;

@Service
@RequiredArgsConstructor
@Qualifier("twoFactorAuthenticationService")
Expand All @@ -36,12 +37,14 @@ public class TwoFactorAuthenticationService implements ManageLoginUseCase {
private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase;
private final ExternalRegisterAccountAccessLogUseCase externalRegisterAccountAccessLogUseCase;
private final ExternalManageRedisTokenUseCase externalManageRedisTokenUseCase;
private final SlackService slackService;
private final ApplicationEventPublisher eventPublisher;
private final JwtTokenProvider jwtTokenProvider;

@Transactional
@Override
public LoginResult authenticate(HttpServletRequest request, TwoFactorAuthenticationRequestDto twoFactorAuthenticationRequestDto) throws LoginFailedException, MemberLockedException {
public LoginResult authenticate(HttpServletRequest request,
TwoFactorAuthenticationRequestDto twoFactorAuthenticationRequestDto)
throws LoginFailedException, MemberLockedException {
String memberId = twoFactorAuthenticationRequestDto.getMemberId();
MemberLoginInfoDto loginMember = externalRetrieveMemberUseCase.getMemberLoginInfoById(memberId);
String totp = twoFactorAuthenticationRequestDto.getTotp();
Expand All @@ -55,9 +58,11 @@ public LoginResult authenticate(HttpServletRequest request, TwoFactorAuthenticat
return LoginResult.create(header, true);
}

private void verifyTwoFactorAuthentication(String memberId, String totp, HttpServletRequest request) throws MemberLockedException, LoginFailedException {
private void verifyTwoFactorAuthentication(String memberId, String totp, HttpServletRequest request)
throws MemberLockedException, LoginFailedException {
if (!manageAuthenticatorUseCase.isAuthenticatorValid(memberId, totp)) {
externalRegisterAccountAccessLogUseCase.registerAccountAccessLog(request, memberId, AccountAccessResult.FAILURE);
externalRegisterAccountAccessLogUseCase.registerAccountAccessLog(request, memberId,
AccountAccessResult.FAILURE);
externalManageAccountLockUseCase.handleLoginFailure(request, memberId);
throw new LoginFailedException("잘못된 인증번호입니다.");
}
Expand All @@ -67,18 +72,21 @@ private void verifyTwoFactorAuthentication(String memberId, String totp, HttpSer
private TokenInfo generateAndSaveToken(MemberLoginInfoDto memberInfo) {
TokenInfo tokenInfo = jwtTokenProvider.generateToken(memberInfo.getMemberId(), memberInfo.getRole());
String clientIpAddress = HttpReqResUtil.getClientIpAddressIfServletRequestExist();
externalManageRedisTokenUseCase.saveToken(memberInfo.getMemberId(), memberInfo.getRole(), tokenInfo, clientIpAddress);
externalManageRedisTokenUseCase.saveToken(memberInfo.getMemberId(), memberInfo.getRole(), tokenInfo,
clientIpAddress);
return tokenInfo;
}

private void sendAdminLoginNotification(HttpServletRequest request, MemberLoginInfoDto loginMember) {
if (loginMember.isSuperAdminRole()) {
slackService.sendAdminLoginNotification(request, loginMember);
eventPublisher.publishEvent(
new NotificationEvent(this, GeneralAlertType.ADMIN_LOGIN, request, loginMember));
}
}

@Override
public LoginResult login(HttpServletRequest request, LoginRequestDto requestDto) throws LoginFailedException, MemberLockedException {
public LoginResult login(HttpServletRequest request, LoginRequestDto requestDto)
throws LoginFailedException, MemberLockedException {
throw new UnsupportedOperationException("Method not implemented");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,29 @@

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.in.RemoveAbnormalAccessIpUseCase;
import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.RemoveIpAccessMonitorPort;
import page.clab.api.global.common.slack.application.SlackService;
import page.clab.api.global.common.slack.domain.SecurityAlertType;
import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent;
import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType;

@Service
@RequiredArgsConstructor
public class AbnormalAccessIpRemoveService implements RemoveAbnormalAccessIpUseCase {

private final RemoveIpAccessMonitorPort removeIpAccessMonitorPort;
private final SlackService slackService;
private final ApplicationEventPublisher eventPublisher;

@Override
@Transactional
public String removeAbnormalAccessIp(HttpServletRequest request, String ipAddress) {
removeIpAccessMonitorPort.deleteById(ipAddress);
slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, "Deleted IP: " + ipAddress);
String abnormalAccessIpDeletedMessage = "Deleted IP: " + ipAddress;
eventPublisher.publishEvent(
new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, request,
abnormalAccessIpDeletedMessage));
return ipAddress;
}
}
Loading

0 comments on commit a9ad4e5

Please sign in to comment.