package com.takensoft.common.util; import com.fasterxml.jackson.databind.ObjectMapper; import com.takensoft.cms.loginPolicy.service.LoginModeService; import com.takensoft.cms.loginPolicy.service.LoginPolicyService; import com.takensoft.cms.mber.service.LgnHstryService; import com.takensoft.cms.mber.vo.LgnHstryVO; import com.takensoft.cms.mber.vo.MberVO; import com.takensoft.cms.token.service.RefreshTokenService; import com.takensoft.cms.token.vo.RefreshTknVO; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import java.io.IOException; import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @author takensoft * @since 2025.03.21 * @modification * since | author | description * 2025.03.21 | takensoft | 최초 등록 * 2025.05.28 | takensoft | 통합 로그인 적용, 문제 해결 * 2025.05.29 | takensoft | Redis 통합 중복로그인 관리 * * 통합 로그인 유틸리티 - Redis 통합 중복로그인 관리 */ @Component @RequiredArgsConstructor @Slf4j public class LoginUtil { private final LgnHstryService lgnHstryService; private final HttpRequestUtil httpRequestUtil; private final LoginModeService loginModeService; private final RefreshTokenService refreshTokenService; private final LoginPolicyService loginPolicyService; private final JWTUtil jwtUtil; private final SessionUtil sessionUtil; private final RedisTemplate redisTemplate; @Value("${jwt.accessTime}") private long JWT_ACCESSTIME; @Value("${jwt.refreshTime}") private long JWT_REFRESHTIME; @Value("${cookie.time}") private int COOKIE_TIME; /** * 통합 로그인 성공 처리 - Redis 기반 중복로그인 관리 */ public void successLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) { try { // 로그인 이력 등록 String loginType = (String) req.getAttribute("loginType"); if (!"OAUTH2".equals(loginType)) { saveLoginHistory(mber, req); } // 로그인 방식 확인 String loginMode = loginModeService.getLoginMode(); log.info("통합 로그인 모드: {}, 사용자: {}", loginMode, mber.getMbrId()); if ("S".equals(loginMode)) { // Redis 기반 중복로그인 관리 적용 handleSessionLogin(mber, req, res); } else { // 기존 Redis 기반 관리 유지 handleJwtLogin(mber, req, res); } res.setHeader("login-type", loginMode); log.info("통합 로그인 성공 처리 완료: {}, 모드: {}", mber.getMbrId(), loginMode); } catch (IOException ioe) { log.error("로그인 응답 처리 중 IO 오류", ioe); throw new RuntimeException(ioe); } catch (Exception e) { log.error("로그인 처리 중 오류 발생", e); throw e; } } /** * 세션 모드 로그인 처리 - Redis 기반 중복로그인 관리 */ private void handleSessionLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) throws IOException { log.info("세션 모드 로그인 처리 (Redis 통합): {}", mber.getMbrId()); // JWT 토큰은 생성하되 세션에만 저장 String accessToken = jwtUtil.createJwt("Authorization", mber.getMbrId(), mber.getLgnId(), mber.getMbrNm(), (List) mber.getAuthorities(), JWT_ACCESSTIME); // 세션 생성 및 정보 저장 HttpSession session = req.getSession(true); session.setAttribute("JWT_TOKEN", accessToken); session.setAttribute("mbrId", mber.getMbrId()); session.setAttribute("mbrNm", mber.getMbrNm()); session.setAttribute("lgnId", mber.getLgnId()); session.setAttribute("roles", mber.getAuthorList()); session.setAttribute("loginType", req.getAttribute("loginType") != null ? req.getAttribute("loginType") : "S"); //중복 로그인 비허용을 Redis로 통합 관리 if (!loginPolicyService.getPolicy()) { handleDuplicateSessionLogin(mber.getMbrId(), session); } // 응답 데이터 구성 (OAuth2는 JSON 응답 없이 리다이렉트만) String loginType = (String) req.getAttribute("loginType"); if (!"OAUTH2".equals(loginType)) { Map result = new HashMap<>(); result.put("mbrId", mber.getMbrId()); result.put("mbrNm", mber.getMbrNm()); result.put("roles", mber.getAuthorList()); res.setContentType("application/json;charset=UTF-8"); res.setStatus(HttpStatus.OK.value()); new ObjectMapper().writeValue(res.getOutputStream(), result); } } /** * Redis 기반 세션 중복로그인 관리 */ private void handleDuplicateSessionLogin(String mbrId, HttpSession newSession) { try { String sessionKey = "session:" + mbrId; // 기존 세션 확인 및 무효화 String oldSessionId = redisTemplate.opsForValue().get(sessionKey); if (oldSessionId != null && !oldSessionId.equals(newSession.getId())) { // 기존 세션 무효화 sessionUtil.invalidateSessionById(oldSessionId); } // 새 세션 정보를 Redis에 저장 redisTemplate.opsForValue().set(sessionKey, newSession.getId(), Duration.ofSeconds(newSession.getMaxInactiveInterval())); // 기존 SessionUtil에도 등록 (호환성 유지) sessionUtil.registerSession(mbrId, newSession); } catch (Exception e) { // 실패해도 로그인은 계속 진행 } } /** * JWT 모드 로그인 처리 - 기존 방식 유지 */ private void handleJwtLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) throws IOException { // JWT 토큰 생성 String accessToken = jwtUtil.createJwt("Authorization", mber.getMbrId(), mber.getLgnId(), mber.getMbrNm(), (List) mber.getAuthorities(), JWT_ACCESSTIME); String refreshToken = jwtUtil.createJwt("refresh", mber.getMbrId(), mber.getLgnId(), mber.getMbrNm(), (List) mber.getAuthorities(), JWT_REFRESHTIME); // Refresh 토큰 처리 RefreshTknVO refresh = new RefreshTknVO(); refresh.setMbrId(mber.getMbrId()); // 기존 refresh 토큰 삭제 if (refreshTokenService.findByCheckRefresh(req, refresh)) { refreshTokenService.delete(req, refresh); } refresh.setToken(refreshToken); // 응답 헤더 및 쿠키 설정 res.setHeader("Authorization", accessToken); res.addCookie(jwtUtil.createCookie("refresh", refreshToken, COOKIE_TIME)); // 중복 로그인 비허용일 때 Redis 저장 if (!loginPolicyService.getPolicy()) { redisTemplate.delete("jwt:" + mber.getMbrId()); redisTemplate.opsForValue().set("jwt:" + mber.getMbrId(), accessToken, JWT_ACCESSTIME, TimeUnit.MILLISECONDS); } // Refresh 토큰 저장 refreshTokenService.saveRefreshToken(req, res, refresh, JWT_REFRESHTIME); // OAuth2가 아닌 경우만 상태 코드 설정 String loginType = (String) req.getAttribute("loginType"); if (!"OAUTH2".equals(loginType)) { res.setStatus(HttpStatus.OK.value()); } } /** * 로그인 이력 저장 */ private void saveLoginHistory(MberVO mber, HttpServletRequest req) { try { String userAgent = httpRequestUtil.getUserAgent(req); LgnHstryVO lgnHstryVO = new LgnHstryVO(); lgnHstryVO.setLgnId(mber.getLgnId()); lgnHstryVO.setLgnType(mber.getAuthorities().stream() .anyMatch(role -> role.getAuthority().equals("ROLE_ADMIN")) ? "0" : "1"); lgnHstryVO.setCntnIp(httpRequestUtil.getIp(req)); lgnHstryVO.setCntnOperSys(httpRequestUtil.getOS(userAgent)); lgnHstryVO.setDeviceNm(httpRequestUtil.getDevice(userAgent)); lgnHstryVO.setBrwsrNm(httpRequestUtil.getBrowser(userAgent)); lgnHstryService.LgnHstrySave(lgnHstryVO); } catch (Exception e) { } } }