hmkim 03-31
250331 김혜민 세션로그인 수정
@b9e46158b27e9e997759694b78e63d69db9e7427
src/main/java/com/takensoft/cms/loginPolicy/service/impl/LoginModeServiceImpl.java
--- src/main/java/com/takensoft/cms/loginPolicy/service/impl/LoginModeServiceImpl.java
+++ src/main/java/com/takensoft/cms/loginPolicy/service/impl/LoginModeServiceImpl.java
@@ -35,7 +35,7 @@
     @Override
     public String getLoginMode() {
 
-        return  loginModeDAO.selectLatestLoginMode();
+        return loginModeDAO.selectLatestLoginMode();
     }
 
     /**
src/main/java/com/takensoft/cms/loginPolicy/web/LoginPolicyController.java
--- src/main/java/com/takensoft/cms/loginPolicy/web/LoginPolicyController.java
+++ src/main/java/com/takensoft/cms/loginPolicy/web/LoginPolicyController.java
@@ -17,6 +17,8 @@
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.Set;
+
 
 /**
  * @author 김혜민
@@ -117,7 +119,7 @@
 
             int result = loginModeService.insertLoginMode(loginModeVO);
 
-           /* if (loginModeVO.getLgnMode().equals("J")) {
+            if (loginModeVO.getLgnMode().equals("J")) {
                 // JWT 전체 로그아웃
                 Set<String> keys = redisTemplate.keys("jwt:*");
                 if (keys != null && !keys.isEmpty()) redisTemplate.delete(keys);
@@ -125,7 +127,7 @@
             } else {
                 // 세션 전체 로그아웃
                 sessionUtil.invalidateAllSessions();
-            }*/
+            }
 
             if (result > 0) {
                 return resUtil.successRes(result, MessageCode.COMMON_SUCCESS);
src/main/java/com/takensoft/cms/token/service/RefreshTokenService.java
--- src/main/java/com/takensoft/cms/token/service/RefreshTokenService.java
+++ src/main/java/com/takensoft/cms/token/service/RefreshTokenService.java
@@ -69,4 +69,11 @@
      * refresh token 전체 삭제
      */
     public int deleteAll();
+
+    /**
+     * @return String - 쿠키에 있는 refresh token 값
+     * @param req - HTTP 요청 객체
+     * 쿠키 내 refresh token 확인
+     */
+    public String getRefreshTokenFromCookie(HttpServletRequest req);
 }
src/main/java/com/takensoft/cms/token/service/impl/RefreshTokenServiceImpl.java
--- src/main/java/com/takensoft/cms/token/service/impl/RefreshTokenServiceImpl.java
+++ src/main/java/com/takensoft/cms/token/service/impl/RefreshTokenServiceImpl.java
@@ -62,17 +62,14 @@
      *
      * refresh token 검증
      */
-    private Map<String, Object> refreshTokenCheck(HttpServletRequest req) {
+     private Map<String, Object> refreshTokenCheck(HttpServletRequest req) {
         Map<String, Object> result = new HashMap <String, Object>();
         // header 방식
 //        String refreshToken = req.getHeader("refresh");
         // 쿠키 방식
-        String refreshToken = null;
-        Cookie[] cookies = req.getCookies();
-        for (Cookie cookie : cookies) {
-            if (cookie.getName().equals("refresh")) refreshToken = cookie.getValue();
-        }
-        if(refreshToken == null) {
+        // 쿠키에서 Refresh Token 가져오기
+        String refreshToken = getRefreshTokenFromCookie(req);
+        if (refreshToken == null) {
             result.put("result", 0);
             return result;
         }
@@ -278,5 +275,21 @@
         }
         return refreshTokenDAO.deleteAll(); // DB에서 리프레시 토큰 전부 삭제
     }
+    /**
+     * @return String - 쿠키에 있는 refresh token 값
+     * @param req - HTTP 요청 객체
+     * 쿠키 내 refresh token 확인
+     */
+    @Override
+    public String getRefreshTokenFromCookie(HttpServletRequest req) {
+        if (req.getCookies() != null) {
+            for (Cookie cookie : req.getCookies()) {
+                if ("refresh".equals(cookie.getName())) {
+                    return cookie.getValue();
+                }
+            }
+        }
+        return null;
+    }
 
 }
src/main/java/com/takensoft/cms/token/web/RefreshTokenController.java
--- src/main/java/com/takensoft/cms/token/web/RefreshTokenController.java
+++ src/main/java/com/takensoft/cms/token/web/RefreshTokenController.java
@@ -68,7 +68,7 @@
             refresh.setMbrId(mbrId);
             int result =  refreshTokenService.delete(req, refresh);
 
-            if ("S".equals(loginType)) {
+            if (loginType.equals("S")) {
                 // 세션 방식: 세션 만료 + SessionMap에서 제거
                 HttpSession session = req.getSession(false);
                 if (session != null) session.invalidate();
src/main/java/com/takensoft/common/config/RedisConfig.java
--- src/main/java/com/takensoft/common/config/RedisConfig.java
+++ src/main/java/com/takensoft/common/config/RedisConfig.java
@@ -1,8 +1,6 @@
 package com.takensoft.common.config;
 
-import com.takensoft.cms.loginPolicy.service.LoginPolicyService;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -29,12 +27,10 @@
     private int redisPort;
 
     @Bean
-    @ConditionalOnProperty(name = "config.allow-multiple-logins", havingValue = "false", matchIfMissing = true) //redis 사용 안 할 경우 빈 등록x
     public RedisConnectionFactory redisConnectionFactory() {
         return new LettuceConnectionFactory(redisHost, redisPort);
     }
     @Bean
-    @ConditionalOnProperty(name = "config.allow-multiple-logins", havingValue = "false", matchIfMissing = true) //redis 사용 안 할 경우 빈 등록x
     public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
         RedisTemplate<String, String> redisTemp = new RedisTemplate<>();
         redisTemp.setConnectionFactory(redisConnectionFactory);
src/main/java/com/takensoft/common/config/SecurityConfig.java
--- src/main/java/com/takensoft/common/config/SecurityConfig.java
+++ src/main/java/com/takensoft/common/config/SecurityConfig.java
@@ -171,7 +171,7 @@
 
         // 로그인 방식에 따라 필터 적용 (JWT or 세션)
         if (loginModeService.getLoginMode().equals("S")) {
-            http.addFilterBefore(new SessionAuthFilter(jwtUtil, redisTemplate, loginPolicyService), LoginFilter.class);
+            http.addFilterBefore(new SessionAuthFilter(jwtUtil, redisTemplate, loginPolicyService, refreshTokenService), LoginFilter.class);
         } else {
             http.addFilterBefore(new JWTFilter(jwtUtil, appConfig, loginPolicyService, redisTemplate), LoginFilter.class);
         }
src/main/java/com/takensoft/common/filter/LoginFilter.java
--- src/main/java/com/takensoft/common/filter/LoginFilter.java
+++ src/main/java/com/takensoft/common/filter/LoginFilter.java
@@ -163,7 +163,7 @@
         refresh.setToken(refreshToken);
 
 
-        if ("S".equals(loginType)) {
+        if (loginType.equals("S")) {
             HttpSession session = req.getSession(true);
             session.setAttribute("JWT_TOKEN", accessToken);
 
src/main/java/com/takensoft/common/filter/SessionAuthFilter.java
--- src/main/java/com/takensoft/common/filter/SessionAuthFilter.java
+++ src/main/java/com/takensoft/common/filter/SessionAuthFilter.java
@@ -3,12 +3,14 @@
 import com.takensoft.cms.loginPolicy.service.LoginPolicyService;
 import com.takensoft.cms.mber.vo.MberAuthorVO;
 import com.takensoft.cms.mber.vo.MberVO;
+import com.takensoft.cms.token.service.RefreshTokenService;
 import com.takensoft.common.util.JWTUtil;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpSession;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -33,10 +35,15 @@
     private final JWTUtil jwtUtil;
     private final RedisTemplate<String, String> redisTemplate;
     private final LoginPolicyService loginPolicyService;
-    public SessionAuthFilter(JWTUtil jwtUtil, RedisTemplate<String, String> redisTemplate, LoginPolicyService loginPolicyService) {
+    private final RefreshTokenService refreshTokenService;
+
+    @Value("${jwt.accessTime}")
+    private long JWT_ACCESSTIME;
+    public SessionAuthFilter(JWTUtil jwtUtil, RedisTemplate<String, String> redisTemplate, LoginPolicyService loginPolicyService, RefreshTokenService refreshTokenService) {
         this.jwtUtil = jwtUtil;
         this.redisTemplate = redisTemplate;
         this.loginPolicyService = loginPolicyService;
+        this.refreshTokenService = refreshTokenService;
     }
     /**
      * @param request HttpServletRequest 객체
@@ -50,20 +57,44 @@
     @Override
     protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
         HttpSession session = request.getSession(false);
+
+        // 세션이 없거나 JWT 토큰이 없으면 인증 실패 처리
         if (session == null || session.getAttribute("JWT_TOKEN") == null) {
             response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
             return;
         }
 
+        // 세션에서 JWT 토큰 가져오기
         String accessToken = (String) session.getAttribute("JWT_TOKEN");
 
-        // 토큰 만료 검증
+        // 만료 여부 검증
         if ((Boolean) jwtUtil.getClaim(accessToken, "isExpired")) {
-            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token expired");
-            return;
+            // 만료된 경우, refresh 토큰 검사
+            String refreshToken = refreshTokenService.getRefreshTokenFromCookie(request);
+
+            if (refreshToken == null || (Boolean) jwtUtil.getClaim(refreshToken, "isExpired")) {
+                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token expired");
+                return;
+            }
+
+            // Refresh 토큰이 유효하다면 새로운 Access 토큰 발급
+            try {
+                String newAccessToken = jwtUtil.createJwt(
+                        "Authorization",
+                        (String) jwtUtil.getClaim(refreshToken, "mbrId"),
+                        (String) jwtUtil.getClaim(refreshToken, "lgnId"),
+                        (String) jwtUtil.getClaim(refreshToken, "mbrNm"),
+                        (List) jwtUtil.getClaim(refreshToken, "roles"),
+                        JWT_ACCESSTIME);
+                session.setAttribute("JWT_TOKEN", newAccessToken);
+                accessToken = newAccessToken;
+            } catch (Exception e) {
+                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Failed to refresh token");
+                return;
+            }
         }
 
-        // 중복 로그인 허용 여부 확인
+        // 중복 로그인 여부 확인 (JWT 방식과 동일하게 Redis 체크)
         if (!loginPolicyService.getPolicy()) {
             String mbrId = (String) jwtUtil.getClaim(accessToken, "mbrId");
             String storedToken = redisTemplate.opsForValue().get("jwt:" + mbrId);
@@ -82,7 +113,6 @@
         mber.setAuthorList(roles);
 
         UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(mber, null, mber.getAuthorities());
-
         SecurityContextHolder.getContext().setAuthentication(authToken);
 
         filterChain.doFilter(request, response);
src/main/java/com/takensoft/common/util/JWTUtil.java
--- src/main/java/com/takensoft/common/util/JWTUtil.java
+++ src/main/java/com/takensoft/common/util/JWTUtil.java
@@ -4,6 +4,7 @@
 import com.takensoft.cms.mber.vo.MberVO;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.JwtException;
 import io.jsonwebtoken.Jwts;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.core.Authentication;
@@ -103,7 +104,6 @@
         Claims claims;
         try {
             claims = Jwts.parser()
-                    .clockSkewSeconds(60)
                     .verifyWith(JWT_SECRET_KEY)
                     .build()
                     .parseSignedClaims(tkn)
@@ -111,6 +111,9 @@
         } catch (ExpiredJwtException e) {
             // 만료된 토큰이라도 claims 꺼내기 가능
             claims = e.getClaims();
+        } catch (JwtException | IllegalArgumentException e) {
+            // 토큰 자체가 잘못된 경우
+            throw new IllegalArgumentException("Invalid token");
         }
 
         switch (knd) {
src/main/java/com/takensoft/common/util/SessionUtil.java
--- src/main/java/com/takensoft/common/util/SessionUtil.java
+++ src/main/java/com/takensoft/common/util/SessionUtil.java
@@ -1,6 +1,7 @@
 package com.takensoft.common.util;
 
 import jakarta.servlet.http.HttpSession;
+import org.springframework.dao.DataAccessException;
 import org.springframework.stereotype.Component;
 
 import java.util.Map;
@@ -20,19 +21,36 @@
 
         private final Map<String, HttpSession> sessionMap = new ConcurrentHashMap<>();
 
+    /**
+     * @param mbrId - 사용자 Id
+     * @param newSession - HTTP 세션
+     *
+     * 기존 세션 있으면 강제 로그아웃
+     */
         public synchronized void registerSession(String mbrId, HttpSession newSession) {
-            // 기존 세션 있으면 강제 로그아웃
             HttpSession oldSession = sessionMap.get(mbrId);
             if (oldSession != null && oldSession != newSession) {
                 oldSession.invalidate();
             }
             sessionMap.put(mbrId, newSession);
         }
-        //로그아웃처리
+    /**
+     * @param mbrId - 사용자 Id
+     *
+     * 로그아웃
+     */
         public void removeSession(String mbrId) {
-            sessionMap.remove(mbrId);
+            HttpSession session = sessionMap.get(mbrId);
+            if (session != null) {
+                session.invalidate();  // 세션 무효화
+            }
+            sessionMap.remove(mbrId); // 이후 맵에서 제거
         }
-        //전체 로그아웃 처리
+
+    /**
+     *
+     * 전체 로그아웃
+     */
         public void invalidateAllSessions() {
             for (HttpSession session : sessionMap.values()) {
                 if (session != null) {
Add a comment
List