한동안 Access Denied가 나오지 않고 잘 굴러가는 줄 알았다.
그런데 또 Access Denied가 나오는 로그를 발견,,ㅠ
[ scheduling-1] c.d.d.notification.NotificationService : Ping 전송 실패 : userId = 13, error = ServletOutputStream failed to flush: java.io.IOException: Broken pipe
[nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] threw exception
org.springframework.security.authorization.AuthorizationDeniedException: Access Denied
[nio-8080-exec-9] o.a.c.c.C.[Tomcat].[localhost] : Exception Processing [ErrorPage[errorCode=0, location=/error]]
jakarta.servlet.ServletException: Unable to handle the Spring Security Exception because the response is already committed.
Caused by: org.springframework.security.authorization.AuthorizationDeniedException: Access Denied
지난번의 필터로 해결될 문제가 아니었나 싶어 심란한 마음에 앞의 상황 맥락과 로그들을 살펴봤다.
이 로그가 나오는 것의 공통점은 사용자가 로그아웃을 하거나 탈퇴를 했을 때였다.
그러면 로그아웃, 회원탈퇴를 했을 때 연결이 제대로 끊어지도록 뭔가 조치를 취해야겠지!
NotificationService 내에
private void removeEmitter(Long userId, SseEmitter emitter, String reason) {
emitters.compute(userId, (key, currentEmitter) -> {
if (currentEmitter == emitter) {
try {
emitter.complete();
} catch (Exception e) {
log.warn("Emitter 종료 중 예외 발생: userId = {}, error = {}", userId, e.getMessage());
}
log.debug("Emitter 제거 완료: userId = {}, 이유 = {}", userId, reason);
return null;
}
return currentEmitter;
});
}
이렇게 제거 메서드를 만들어두고, 이 메서드를 사용하여 연결을 끊는 메서드를 만들었다.
public void disconnectEmitter(Long userId) {
SseEmitter emitter = emitters.remove(userId);
if (emitter != null) {
removeEmitter(userId, emitter, "로그아웃/탈퇴에 의한 SSE 연결 종료");
}
}
작성하다보니 뭔가.... 부족한 게 느껴지는데 좀 더 보완해야겠다ㅠ
뭔가 명확하질 않네...
아무튼 흐름은 깨달았고, 제대로 돌아가긴 한다.
그리고 로그아웃을 한 후, 회원탈퇴로 사용자를 완전히 delete 하기 전에 disconnectEmitter()를 넣어주면 되겠다!
notificationService.disconnectEmitter(user.getId());
log.info("SSE 연결 종료(로그아웃) : userId = {}", user.getId());
log.info("로그아웃 성공");
notificationService.disconnectEmitter(user.getId());
log.info("SSE 연결 종료(회원탈퇴) : userId = {}", user.getId());
// 사용자 삭제
userRepository.delete(user);
log.info("회원탈퇴 완료");
(원래는 UserService 내에 로그인, 로그아웃, 회원탈퇴 등의 인증 관련 로직을 함께 관리하고 있었는데,
NotificationService에서 UserService를 참조하고, 이번 작업으로 인해 UserService가 다시 NotificationService를 참조하게 되어 순환 참조 문제가 발생했다.
이를 해결하기 위해 인증 관련 기능은 AuthService로 따로 분리했다.)
그러면 이제 Access Denied 예외는 더이상 터지지 않게 된다!