박민혁 박민혁 07-11
250711 박민혁 로그아웃 기능 추가
@9ee0ee2b61c41dfda89cdcab267bdfe351846e67
src/main/java/kr/co/takensoft/ai/system/auth/service/RefreshService.java
--- src/main/java/kr/co/takensoft/ai/system/auth/service/RefreshService.java
+++ src/main/java/kr/co/takensoft/ai/system/auth/service/RefreshService.java
@@ -20,4 +20,18 @@
      * 토큰 재발급
      */
     String tokenReissueProc(HttpServletRequest req, HttpServletResponse res) throws Exception;
+    /**
+     * @param req  클라이언트 요청
+     * @return String 쿠키 정보
+     *
+     * 쿠키의 리프레시 토큰 추출
+     */
+    String getRefreshTokenFromCookie(HttpServletRequest request);
+
+    /**
+     * @param res  클라이언트 요청 응답
+     *
+     * 쿠키의 리프레시 토큰 삭제
+     */
+    void clearRefreshTokenCookie(HttpServletResponse response);
 }
src/main/java/kr/co/takensoft/ai/system/auth/service/impl/AuthServiceImpl.java
--- src/main/java/kr/co/takensoft/ai/system/auth/service/impl/AuthServiceImpl.java
+++ src/main/java/kr/co/takensoft/ai/system/auth/service/impl/AuthServiceImpl.java
@@ -67,7 +67,7 @@
      * @param LoginDTO 로그인 정보
      * @return Map<String, String> 액세스 토큰과 리프레시 토큰
      *
-     * 사용자 회원가입
+     * 사용자 로그인
      */
     public Map<String, String> memberLogin(LoginDTO loginDTO) throws Exception{
         MemberVO member = authDAO.findMemberInfo(loginDTO.getLoginId());
src/main/java/kr/co/takensoft/ai/system/auth/service/impl/RefreshServiceImpl.java
--- src/main/java/kr/co/takensoft/ai/system/auth/service/impl/RefreshServiceImpl.java
+++ src/main/java/kr/co/takensoft/ai/system/auth/service/impl/RefreshServiceImpl.java
@@ -1,6 +1,7 @@
 package kr.co.takensoft.ai.system.auth.service.impl;
 
 import io.jsonwebtoken.ExpiredJwtException;
+import jakarta.servlet.http.Cookie;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import kr.co.takensoft.ai.system.auth.dao.AuthDAO;
@@ -36,7 +37,7 @@
     private final RefreshDAO refreshDAO;
 
     public String tokenReissueProc(HttpServletRequest req, HttpServletResponse res) throws Exception {
-        String refreshToken = req.getHeader("RefreshToken");
+        String refreshToken = getRefreshTokenFromCookie(req);
         if (refreshToken == null) {
             throw new Exception("Refresh token이 전달되지 않았습니다.");
         }
@@ -53,16 +54,20 @@
             // 만료 여부 체크
             if (jwtUtil.isExpired(refreshToken)) {
                 refreshDAO.deleteRefreshToken(memberId);
+                // 만료된 쿠키 삭제
+                clearRefreshTokenCookie(res);
                 throw new Exception("Refresh token이 만료되었습니다.");
             }
         } catch (ExpiredJwtException e) {
             // 여기서도 따로 만료된 경우 캐치
             refreshDAO.deleteRefreshToken(memberId);
+            clearRefreshTokenCookie(res);
             throw new Exception("Refresh token이 만료되었습니다.");
         }
 
         // 리프레시 토큰이 DB에 존재하는지 확인
         if (refreshDAO.getRefreshTokenByUserId(memberId) == null) {
+            clearRefreshTokenCookie(res);
             throw new Exception("리프레시 토큰이 존재하지 않습니다.");
         }
 
@@ -70,6 +75,7 @@
         MemberVO member = authDAO.findMemberInfo(memberId);
         if (member == null) {
             refreshDAO.deleteRefreshToken(memberId); // 자동 로그아웃
+            clearRefreshTokenCookie(res);
             throw new Exception("유효한 사용자가 아닙니다.");
         }
 
@@ -84,4 +90,35 @@
 
         return newAccessToken;
     }
+
+    /**
+     * @param req  클라이언트 요청
+     *
+     * 쿠키의 리프레시 토큰 추출
+     */
+    public String getRefreshTokenFromCookie(HttpServletRequest request) {
+        Cookie[] cookies = request.getCookies();
+        if (cookies != null) {
+            for (Cookie cookie : cookies) {
+                if ("refreshToken".equals(cookie.getName())) {
+                    return cookie.getValue();
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param res  클라이언트 요청 응답
+     *
+     * 쿠키의 리프레시 토큰 삭제
+     */
+    public void clearRefreshTokenCookie(HttpServletResponse response) {
+        Cookie cookie = new Cookie("refreshToken", null);
+        //cookie.setHttpOnly(true);
+        //cookie.setSecure(true);
+        cookie.setPath("/");
+        cookie.setMaxAge(0); // 즉시 만료
+        response.addCookie(cookie);
+    }
 }
src/main/java/kr/co/takensoft/ai/system/auth/vo/MemberVO.java
--- src/main/java/kr/co/takensoft/ai/system/auth/vo/MemberVO.java
+++ src/main/java/kr/co/takensoft/ai/system/auth/vo/MemberVO.java
@@ -20,13 +20,14 @@
 @AllArgsConstructor
 @NoArgsConstructor
 public class MemberVO {
-    private String memberId;
-    private String loginId;
-    private String password;
-    private String email;
-    private String phoneNumber;
-    private String memberName;
-    private String salt;
-    private String createdAt;
-    private String updatedDt;
+    private String memberId; // 사용자 아이디
+    private String loginId; // 로그인 아이디
+    private String password; // 비밀번호
+    private String email; // 이메일
+    private String phoneNumber; // 전화번호
+    private String memberName; // 사용자 이름
+    private String salt; // 고유 솔트값
+    private String createdAt; // 생성일
+    private String updatedDt; // 수정일
+    private String useAt; // 사용여부
 }
src/main/java/kr/co/takensoft/ai/system/auth/web/AuthController.java
--- src/main/java/kr/co/takensoft/ai/system/auth/web/AuthController.java
+++ src/main/java/kr/co/takensoft/ai/system/auth/web/AuthController.java
@@ -1,8 +1,14 @@
 package kr.co.takensoft.ai.system.auth.web;
 
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import kr.co.takensoft.ai.system.auth.dao.RefreshDAO;
 import kr.co.takensoft.ai.system.auth.dto.LoginDTO;
 import kr.co.takensoft.ai.system.auth.service.AuthService;
+import kr.co.takensoft.ai.system.auth.service.RefreshService;
 import kr.co.takensoft.ai.system.auth.vo.MemberVO;
+import kr.co.takensoft.ai.system.common.util.JwtUtil;
 import lombok.RequiredArgsConstructor;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -29,6 +35,9 @@
 public class AuthController {
 
     private final AuthService authService;
+    private final RefreshService refreshService;
+    private final RefreshDAO refreshDAO;
+    private final JwtUtil jwtUtil;
 
     /**
      * @param member 사용자 정보
@@ -50,12 +59,20 @@
      * 사용자 로그인
      */
     @PostMapping("/login.json")
-    public ResponseEntity<?> login(@RequestBody LoginDTO loginDTO) throws Exception {
+    public ResponseEntity<?> login(@RequestBody LoginDTO loginDTO, HttpServletResponse response) throws Exception {
         HashMap<String, Object> result = new HashMap<>();
         try {
             Map<String, String> tokens = authService.memberLogin(loginDTO);
             result.put("accessToken", tokens.get("accessToken"));
-            result.put("refreshToken", tokens.get("refreshToken"));
+            Cookie refreshTokenCookie = new Cookie("refreshToken", tokens.get("refreshToken"));
+            //refreshTokenCookie.setHttpOnly(true);  // JavaScript 접근 차단
+            //refreshTokenCookie.setSecure(true);    // HTTPS에서만 전송
+            refreshTokenCookie.setPath("/");       // 전체 경로에서 사용
+            refreshTokenCookie.setMaxAge(24 * 60 * 60); // 1일 (초 단위)
+            refreshTokenCookie.setAttribute("SameSite", "Strict"); // CSRF 방지
+
+            response.addCookie(refreshTokenCookie);
+
             return new ResponseEntity<>(result, HttpStatus.OK);
         } catch (Exception e) {
             result.put("message", e.getMessage());
@@ -63,5 +80,39 @@
         }
     }
 
+    /**
+     * @param req, res 로그아웃 요청과 응답
+     * @return ResponseEntity 로그아웃 결과
+     *
+     * 사용자 로그인
+     */
+    @PostMapping("/logout.json")
+    public ResponseEntity<?> logout(HttpServletRequest req, HttpServletResponse res) {
+        try {
+            // 쿠키에서 리프레시 토큰 추출
+            String refreshToken = refreshService.getRefreshTokenFromCookie(req);
+            if (refreshToken != null) {
+                String memberId = jwtUtil.getUsid(refreshToken);
+                // DB에서 리프레시 토큰 삭제
+                refreshDAO.deleteRefreshToken(memberId);
+            }
+
+            // 쿠키 삭제
+            Cookie cookie = new Cookie("refreshToken", null);
+            cookie.setHttpOnly(true);
+            cookie.setSecure(true);
+            cookie.setPath("/");
+            cookie.setMaxAge(0);
+            res.addCookie(cookie);
+
+            Map<String, Object> result = new HashMap<>();
+            result.put("message", "로그아웃이 완료되었습니다.");
+            return new ResponseEntity<>(result, HttpStatus.OK);
+        } catch (Exception e) {
+            Map<String, Object> result = new HashMap<>();
+            result.put("message", "로그아웃 처리 중 오류가 발생했습니다.");
+            return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
 
 }
(No newline at end of file)
src/main/resources/mybatis/mapper/auth/auth-SQL.xml
--- src/main/resources/mybatis/mapper/auth/auth-SQL.xml
+++ src/main/resources/mybatis/mapper/auth/auth-SQL.xml
@@ -16,7 +16,8 @@
             phone_number,
             member_name,
             created_at,
-            updated_at
+            updated_at,
+            use_at
             )
         VALUES (
             #{memberId},
@@ -27,7 +28,8 @@
             #{phoneNumber},
             #{memberName},
             CURRENT_TIMESTAMP,
-            CURRENT_TIMESTAMP
+            CURRENT_TIMESTAMP,
+            'Y'
         )
     </insert>
 
Add a comment
List