Skip to content

Commit

Permalink
슬랙 메시지 개선 완료 (#347)
Browse files Browse the repository at this point in the history
* refactor(Slack): 중복 로그인에 대한 경고 대상을 관리자 이상의 멤버로 제한 및 사용자 아이디를 불러올 수 있도록 변경

* refactor(Slack): 지속적인 로그인 실패에 대한 경고 대상을 관리자 이상의 멤버로 제한 및 사용자 아이디를 불러올 수 있도록 변경

* refactor(Slack): 미리보기에 알림 제목이 표시되도록 개선
  • Loading branch information
limehee authored May 13, 2024
1 parent f5c2f41 commit 83af919
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ public void handleAccountLockInfo(String memberId) throws MemberLockedException,

@Transactional
public void handleLoginFailure(HttpServletRequest request, String memberId) throws MemberLockedException, LoginFaliedException {
Member member = memberService.getMemberById(memberId);
AccountLockInfo accountLockInfo = ensureAccountLockInfoForMemberId(memberId);
validateAccountLockStatus(accountLockInfo);
accountLockInfo.incrementLoginFailCount();
if (accountLockInfo.shouldBeLocked(maxLoginFailures)) {
accountLockInfo.lockAccount(lockDurationMinutes);
slackService.sendSecurityAlertNotification(request, SecurityAlertType.REPEATED_LOGIN_FAILURES,
"[" + accountLockInfo.getMember().getId() + "/" + accountLockInfo.getMember().getName() + "]" + " 로그인 실패 횟수 초과로 계정이 잠겼습니다.");
sendSlackMessage(request, member);
}
accountLockInfoRepository.save(accountLockInfo);
}
Expand All @@ -109,4 +109,11 @@ private void validateAccountLockStatus(AccountLockInfo accountLockInfo) throws M
}
}

private void sendSlackMessage(HttpServletRequest request, Member member) {
if (member.isAdminRole()) {
request.setAttribute("member", member.getId() + " " + member.getName());
slackService.sendSecurityAlertNotification(request, SecurityAlertType.REPEATED_LOGIN_FAILURES, "로그인 실패 횟수 초과로 계정이 잠겼습니다.");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ public boolean isSameIp(String ip) {
return this.ip.equals(ip);
}

public boolean isAdminToken() {
return role == Role.ADMIN || role == Role.SUPER;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private boolean authenticateToken(HttpServletRequest request, HttpServletRespons
}
if (!redisToken.getIp().equals(clientIpAddress)) {
redisTokenService.deleteRedisTokenByAccessToken(token);
slackService.sendSecurityAlertNotification(request, SecurityAlertType.DUPLICATE_LOGIN, "토큰 발급 IP와 다른 IP에서 접속하여 토큰을 삭제하였습니다.");
sendSlackMessage(request, redisToken);
log.warn("[{}] 토큰 발급 IP와 다른 IP에서 접속하여 토큰을 삭제하였습니다.", clientIpAddress);
ResponseUtil.sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED);
return false;
Expand All @@ -90,4 +90,11 @@ private boolean authenticateToken(HttpServletRequest request, HttpServletRespons
}
}

private void sendSlackMessage(HttpServletRequest request, RedisToken redisToken) {
if (redisToken.isAdminToken()) {
request.setAttribute("member", redisToken.getId());
slackService.sendSecurityAlertNotification(request, SecurityAlertType.DUPLICATE_LOGIN, "토큰 발급 IP와 다른 IP에서 접속하여 토큰을 삭제하였습니다.");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.slack.api.Slack;
import com.slack.api.model.Attachment;
import com.slack.api.model.Attachments;
import static com.slack.api.model.block.Blocks.actions;
import static com.slack.api.model.block.Blocks.section;
import com.slack.api.model.block.LayoutBlock;
Expand All @@ -16,6 +15,7 @@
import io.ipinfo.spring.strategies.attribute.AttributeStrategy;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
Expand All @@ -37,6 +37,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -97,10 +98,11 @@ public void sendServerStartNotification() {
private CompletableFuture<Boolean> sendSlackMessageWithBlocks(List<LayoutBlock> blocks) {
return CompletableFuture.supplyAsync(() -> {
Payload payload = Payload.builder()
.blocks(List.of(blocks.getFirst()))
.attachments(Collections.singletonList(
Attachment.builder()
.color(color)
.blocks(blocks)
.blocks(blocks.subList(1, blocks.size()))
.build()
)).build();
try {
Expand All @@ -121,9 +123,7 @@ private CompletableFuture<Boolean> sendSlackMessageWithBlocks(List<LayoutBlock>
private List<LayoutBlock> createErrorBlocks(HttpServletRequest request, Exception e) {
String httpMethod = request.getMethod();
String requestUrl = request.getRequestURI();

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = (authentication == null || authentication.getName() == null) ? "anonymous" : authentication.getName();
String username = getUsername();

String errorMessage = e.getMessage() == null ? "No error message provided" : e.getMessage();
String detailedMessage = extractMessageAfterException(errorMessage);
Expand All @@ -142,11 +142,8 @@ private List<LayoutBlock> createErrorBlocks(HttpServletRequest request, Exceptio
private List<LayoutBlock> createSecurityAlertBlocks(HttpServletRequest request, SecurityAlertType alertType, String additionalMessage) {
String clientIpAddress = HttpReqResUtil.getClientIpAddressIfServletRequestExist();
String requestUrl = request.getRequestURI();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = (authentication == null || authentication.getName() == null) ? "anonymous" : authentication.getName();

IPResponse ipResponse = attributeStrategy.getAttribute(request);
String location = ipResponse == null ? "Unknown" : ipResponse.getCountryName() + ", " + ipResponse.getCity();
String username = getUsername(request);
String location = getLocation(request);

return Arrays.asList(
section(section -> section.text(markdownText(String.format(":imp: *%s*", alertType.getTitle())))),
Expand All @@ -162,8 +159,7 @@ private List<LayoutBlock> createSecurityAlertBlocks(HttpServletRequest request,

private List<LayoutBlock> createAdminLoginBlocks(HttpServletRequest request, Member loginMember) {
String clientIpAddress = HttpReqResUtil.getClientIpAddressIfServletRequestExist();
IPResponse ipResponse = attributeStrategy.getAttribute(request);
String location = ipResponse == null ? "Unknown" : ipResponse.getCountryName() + ", " + ipResponse.getCity();
String location = getLocation(request);

return Arrays.asList(
section(section -> section.text(markdownText(String.format(":mechanic: *%s Login*", loginMember.getRole().getDescription())))),
Expand Down Expand Up @@ -249,4 +245,23 @@ private String formatMemoryUsage(MemoryUsage memoryUsage) {
return String.format("%dMB / %dMB (%.2f%%)", usedMemory, maxMemory, ((double) usedMemory / maxMemory) * 100);
}

private static String getUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (authentication == null || authentication.getName() == null) ? "anonymous" : authentication.getName();
}

private @NotNull String getUsername(HttpServletRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return Optional.ofNullable(request.getAttribute("member"))
.map(Object::toString)
.orElseGet(() -> Optional.ofNullable(authentication)
.map(Authentication::getName)
.orElse("anonymous"));
}

private @NotNull String getLocation(HttpServletRequest request) {
IPResponse ipResponse = attributeStrategy.getAttribute(request);
return ipResponse == null ? "Unknown" : ipResponse.getCountryName() + ", " + ipResponse.getCity();
}

}

0 comments on commit 83af919

Please sign in to comment.