
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
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.loginPolicy.service.StorageModeService;
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<String, String> redisTemplate;
private final StorageModeService storageModeService;
@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();
if ("S".equals(loginMode)) {
// Redis 기반 중복로그인 관리 적용
handleSessionLogin(mber, req, res);
} else {
// 기존 Redis 기반 관리 유지
handleJwtLogin(mber, req, res);
}
// 스토리지 저장 방식 확인
String storageMode = storageModeService.findByStorageMode();
res.setHeader("login-type", loginMode);
res.setHeader("storage-type", storageMode);
log.info("통합 로그인 성공 처리 완료: {}, 로그인 모드: {}, 스토리지 모드: {}", mber.getMbrId(), loginMode, storageMode);
}
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<String, Object> 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) {
}
}
}