
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.mber.service.LgnHstryService;
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.Set;
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 통합 중복로그인 관리
* 2025.06.04 | takensoft | Redis 트랜잭션 및 타이밍 이슈 해결
* 2025.06.09 | takensoft | 중복로그인 처리 로직 개선
* 2025.06.18 | takensoft | 로그인 이력 저장 로직 통합 및 중복 제거
*
* 통합 로그인 유틸리티 - 로그인 이력 저장 로직 중복 제거
*/
@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;
@Value("${jwt.accessTime}")
private long JWT_ACCESSTIME;
@Value("${jwt.refreshTime}")
private long JWT_REFRESHTIME;
@Value("${cookie.time}")
private int COOKIE_TIME;
/**
* 통합 로그인 성공 처리
*/
public void successLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) throws IOException {
String loginMode = loginModeService.getLoginMode();
boolean allowMultipleLogin = loginPolicyService.getPolicy();
res.setHeader("loginMode", loginMode); // J, S
res.setHeader("policyMode", allowMultipleLogin ? "Y" : "N"); // Y, N
// 통합된 로그인 이력 저장 로직 사용
String loginType = (String) req.getAttribute("loginType");
if (!"OAUTH2".equals(loginType)) {
// 시스템 로그인인 경우에만 이력 저장 (OAuth2는 핸들러에서 처리)
lgnHstryService.saveSystemLoginHistory(mber, req);
}
if ("S".equals(loginMode)) {
handleSessionLogin(mber, req, res);
} else {
handleJwtLogin(mber, req, res);
}
}
/**
* 세션 모드 로그인 처리 - 중복로그인 개선
*/
private void handleSessionLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) throws IOException {
String mbrId = mber.getMbrId();
// 중복로그인 비허용 시 기존 세션 정리
if (!loginPolicyService.getPolicy()) {
forceLogoutExistingSession(mbrId);
}
// JWT 토큰은 생성하되 세션에만 저장
String accessToken = jwtUtil.createJwt("Authorization", mbrId, mber.getLgnId(),
mber.getMbrNm(), (List) mber.getAuthorities(), JWT_ACCESSTIME);
// 세션 생성 및 정보 저장
HttpSession session = req.getSession(true);
session.setAttribute("JWT_TOKEN", accessToken);
session.setAttribute("mbrId", mbrId);
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()) {
saveSessionTokenToRedis(mbrId, accessToken, session.getMaxInactiveInterval());
}
// SessionUtil에 등록
sessionUtil.registerSession(mbrId, session);
// OAuth2가 아닌 경우 JSON 응답 전송
String loginType = (String) req.getAttribute("loginType");
if (!"OAUTH2".equals(loginType)) {
sendSessionLoginResponse(res, mber);
}
}
/**
* JWT 모드 로그인 처리 - 중복로그인 개선
*/
private void handleJwtLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) throws IOException {
String mbrId = mber.getMbrId();
// 중복로그인 비허용 시 기존 토큰 정리
if (!loginPolicyService.getPolicy()) {
forceLogoutExistingJWT(mbrId);
}
// JWT 토큰 생성
String accessToken = jwtUtil.createJwt("Authorization", mbrId, mber.getLgnId(),
mber.getMbrNm(), (List) mber.getAuthorities(), JWT_ACCESSTIME);
String refreshToken = jwtUtil.createJwt("refresh", mbrId, mber.getLgnId(),
mber.getMbrNm(), (List) mber.getAuthorities(), JWT_REFRESHTIME);
// Refresh 토큰 처리
RefreshTknVO refresh = new RefreshTknVO();
refresh.setMbrId(mbrId);
// 기존 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에 JWT 저장 (중복로그인 관리용)
if (!loginPolicyService.getPolicy()) {
saveJWTTokenToRedis(mbrId, accessToken);
}
// 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 forceLogoutExistingSession(String mbrId) {
// 1. SessionUtil에서 기존 세션 제거
sessionUtil.removeSession(mbrId);
// 2. Redis에서 기존 세션 토큰 삭제
String sessionTokenKey = "session_token:" + mbrId;
String existingToken = redisTemplate.opsForValue().get(sessionTokenKey);
if (existingToken != null) {
redisTemplate.delete(sessionTokenKey);
}
// 3. 기타 세션 관련 키 정리
cleanupSessionRedisKeys(mbrId);
}
/**
* 기존 JWT 강제 로그아웃
*/
private void forceLogoutExistingJWT(String mbrId) {
String jwtKey = "jwt:" + mbrId;
String existingToken = redisTemplate.opsForValue().get(jwtKey);
if (existingToken != null) {
redisTemplate.delete(jwtKey);
}
}
/**
* Redis에 세션 토큰 저장
*/
private void saveSessionTokenToRedis(String mbrId, String accessToken, int sessionTimeout) {
String tokenKey = "session_token:" + mbrId;
redisTemplate.opsForValue().set(tokenKey, accessToken, Duration.ofSeconds(sessionTimeout));
}
/**
* Redis에 JWT 토큰 저장
*/
private void saveJWTTokenToRedis(String mbrId, String accessToken) {
String jwtKey = "jwt:" + mbrId;
redisTemplate.opsForValue().set(jwtKey, accessToken, JWT_ACCESSTIME, TimeUnit.MILLISECONDS);
}
/**
* 세션 관련 Redis 키 정리
*/
private void cleanupSessionRedisKeys(String mbrId) {
Set<String> sessionKeys = redisTemplate.keys("session*:" + mbrId);
if (sessionKeys != null && !sessionKeys.isEmpty()) {
redisTemplate.delete(sessionKeys);
}
}
/**
* 세션 로그인 응답 전송
*/
private void sendSessionLoginResponse(HttpServletResponse res, MberVO mber) throws IOException {
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.setCharacterEncoding("UTF-8");
res.setStatus(HttpStatus.OK.value());
String jsonResponse = new ObjectMapper().writeValueAsString(result);
res.getWriter().write(jsonResponse);
res.getWriter().flush();
}
}