package com.takensoft.common.util; import jakarta.servlet.http.HttpSession; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author takensoft * @since 2025.03.21 * @modification * since | author | description * 2025.03.21 | takensoft | 최초 등록 * 2025.05.29 | takensoft | Redis 통합 중복로그인 관리 * * 세션 로그인 방식의 유틸리티 - Redis 통합 */ @Component public class SessionUtil { private final Map sessionMap = new ConcurrentHashMap<>(); private final RedisTemplate redisTemplate; public SessionUtil(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } /** * 세션 등록 - Redis 연동 * 기존 세션 있으면 강제 로그아웃 후 새 세션 등록 */ public synchronized void registerSession(String mbrId, HttpSession newSession) { try { // 1. 기존 메모리 세션 처리 HttpSession oldSession = sessionMap.get(mbrId); if (oldSession != null && oldSession != newSession) { try { oldSession.invalidate(); } catch (IllegalStateException e) { } } // 2. 새 세션을 메모리에 등록 sessionMap.put(mbrId, newSession); // 3. Redis에 세션 정보 저장 (중복로그인 관리용) String sessionKey = "session:" + mbrId; redisTemplate.opsForValue().set(sessionKey, newSession.getId(), Duration.ofSeconds(newSession.getMaxInactiveInterval())); } catch (Exception e) { e.printStackTrace(); } } /** * 세션 ID로 세션 무효화 */ public void invalidateSessionById(String sessionId) { try { boolean found = false; // 메모리에서 해당 세션 ID를 가진 세션 찾아서 무효화 sessionMap.entrySet().removeIf(entry -> { HttpSession session = entry.getValue(); if (session != null && session.getId().equals(sessionId)) { try { session.invalidate(); return true; } catch (IllegalStateException e) { return true; } } return false; }); } catch (Exception e) { e.printStackTrace(); } } /** * 사용자별 세션 제거 - Redis 연동 */ public void removeSession(String mbrId) { try { // 1. 메모리 세션 무효화 HttpSession session = sessionMap.get(mbrId); if (session != null) { try { session.invalidate(); } catch (IllegalStateException e) { e.printStackTrace(); } } sessionMap.remove(mbrId); // 2. Redis에서도 제거 String sessionKey = "session:" + mbrId; redisTemplate.delete(sessionKey); } catch (Exception e) { e.printStackTrace(); } } /** * 전체 세션 무효화 - Redis 연동 */ public void invalidateAllSessions() { try { // 1. 모든 메모리 세션 무효화 for (Map.Entry entry : sessionMap.entrySet()) { HttpSession session = entry.getValue(); if (session != null) { try { session.invalidate(); } catch (IllegalStateException e) { e.printStackTrace(); } } } sessionMap.clear(); // 2. Redis에서 모든 세션 키 삭제 try { var sessionKeys = redisTemplate.keys("session:*"); if (sessionKeys != null && !sessionKeys.isEmpty()) { redisTemplate.delete(sessionKeys); } } catch (Exception e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } } /** * 현재 활성 세션 수 조회 */ public int getActiveSessionCount() { return sessionMap.size(); } /** * 특정 사용자의 세션 존재 여부 확인 */ public boolean hasActiveSession(String mbrId) { HttpSession session = sessionMap.get(mbrId); if (session == null) { return false; } try { // 세션이 유효한지 확인 session.getAttribute("mbrId"); return true; } catch (IllegalStateException e) { // 세션이 무효화됨 sessionMap.remove(mbrId); return false; } } /** * Redis에서 세션 정보 확인 */ public boolean isValidSessionInRedis(String mbrId, String sessionId) { try { String sessionKey = "session:" + mbrId; String storedSessionId = redisTemplate.opsForValue().get(sessionKey); return storedSessionId != null && storedSessionId.equals(sessionId); } catch (Exception e) { return false; } } }