모눈종이에 사각사각
HttpSessionListener 사용하기 본문
HttpSessionListener
같은 아이디로 중복 로그인을 했을 경우, 먼저 들어온 사용자의 세션을 차단하는 기능을 만들어야 하는 상황입니다. 어떻게 구현하면 좋을까 검색을 하다가 HttpSessionListener를 사용해서 구현하는 방법을 알게 되었고, 이를 공부하면서 알게 된 점을 글로 작성해보았습니다.
HttpSessionListener란?
HttpSessionListener는 Java Servlet API에서 제공하는 인터페이스 중 하나로, 웹 애플리케이션에서 HttpSession 객체의 생명주기 이벤트를 감지하고 처리할 수 있도록 해줍니다. 즉, 세션이 생성되거나 소멸될 때 이를 감지하여 특정 로직을 실행할 수 있는 기능을 제공합니다. HttpSessionListener는 javax.servlet 패키지에 포함되어 있으며, 다음 두 가지 메서드를 제공합니다
- default void sessionCreated(HttpSessionEvent se)
- 새로운 세션이 생성될 때 호출됩니다.
- 사용자 인증, 초기 설정, 세션 관리 로직 등을 실행할 수 있습니다.
- default void sessionDestroyed(HttpSessionEvent se)
- 세션이 소멸될 때 호출됩니다.
- 리소스 정리, 로그 기록, 세션 통계 계산 등을 처리하는 데 유용합니다.
동작 원리
- 세션 생성 감지
- 사용자가 웹 애플리케이션에 접근할 때 새로운 세션이 필요하면 sessionCreated 메서드가 호출됩니다.
- 세션 소멸 감지
- 세션이 만료되거나 명시적으로 제거될 때 sessionDestroyed 메서드가 호출됩니다.
- 이 이벤트는 서버의 설정된 세션 타임아웃에 의해 자동으로 발생하거나, HttpSession.invalidate() 메서드를 호출하여 강제로 발생시킬 수 있습니다.
HttpSessionListener 사용 방법
- HttpSessionListener를 구현한 SessionUtil 클래스 생성
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class SessionUtil implements HttpSessionListener {
private final Logger logger = LoggerFactory.getLogger(SessionUtil.class);
@Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
sessions.put(session.getId(), session);
logger.debug("sessionCreated :: {} ", session.getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
String sessionId = event.getSession().getId();
sessions.remove(sessionId);
logger.debug("sessionDestroyed :: {} ", sessionId);
}
}
- web.xml에 등록
<web-app>
<listener>
<listener-class>com.example.SessionUtil</listener-class>
</listener>
</web-app>
멀티스레드 환경을 고려한 SessionUtil
중복 로그인 구현 시, 다수의 사용자가 동시에 로그인을 시도하거나 로그아웃할 때 세션 정보를 관리하는 과정에서 동시성 문제가 발생할 수 있습니다. 이를 방지하기 위해 ConcurrentHashMap을 사용해 스레드 안전성을 보장하고, 필요한 경우 synchronized 키워드로 추가 동기화를 적용했습니다.
public class SessionUtil implements HttpSessionListener {
private static final Map<String, HttpSession> sessions = new ConcurrentHashMap<>();
private final Logger logger = LoggerFactory.getLogger(SessionUtil.class);
@Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
sessions.put(session.getId(), session);
logger.debug("sessionCreated :: sessionId={}, userId={}", session.getId(), session.getAttribute("USER_ID"));
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
String sessionId = event.getSession().getId();
sessions.remove(sessionId);
logger.debug("sessionDestroyed :: sessionId={} ", sessionId);
}
public synchronized static void removeSessionForDoubleLogin(String userId, String nowSessionId) {
for (String key : sessions.keySet()) {
HttpSession session = sessions.get(key);
if (
userId.equals((String) session.getAttribute("USER_ID"))
&& !nowSessionId.equals(session.getId())
) {
sessions.remove(session.getId());
break;
}
}
}
public static boolean isValidSession(String sessionId) {
return sessions.containsKey(sessionId);
}
}
- 사용자가 로그인을 할 경우 사용자의 Id를 세션에 담게 됩니다. 따라서 세션에서 USER_ID에 해당하는 값으로 어떤 사용자인지 파악할 수 있습니다.
- 중복 로그인 세션을 지울 때 먼저 sessions Map 안에 현재 진입한 사용자의 id와 같은 id를 사용하는 세션이 있는지 찾습니다.(sessions에 이미 자신의 세션도 들어가 있으므로, 그 경우는 제외합니다.)
- 그리고 같은게 있다면 기존에 있던 세션을 제거합니다.
세션을 확인하고 지우는 과정에서 여러 스레드가 접근할 수 있으므로 synchroinized 키워드를 사용해 하나의 스레드만 처리할 수 있도록 했습니다.
아직 개선할 점이 많은 코드이지만, 전반적인 흐름을 잡고 다음 단계로 나아가는 데 도움이 되었으면 합니다.
'활동기록' 카테고리의 다른 글
2024 회고 및 2025 다짐 (2) | 2025.02.02 |
---|---|
JSP 페이지의 실행 순서와 컴파일 과정 이해하기 : JSP 오류 해결 (0) | 2024.12.22 |
NAS 마운트 하기 (0) | 2024.12.09 |
⌜개발자 오늘도 마음 튼튼하게 성장하기⌟를 읽고 (1) | 2024.11.24 |
2024 인프콘 강의 - 경력이 늘수록 CS이론이 중요해지는 이유(최호성, 널널한 개발자) (0) | 2024.11.10 |
Comments