diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/User/exception/GlobalExceptionHandler.java b/backend/pirocheck/src/main/java/backend/pirocheck/User/exception/GlobalExceptionHandler.java index a135034..4fd3755 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/User/exception/GlobalExceptionHandler.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/User/exception/GlobalExceptionHandler.java @@ -9,6 +9,7 @@ @RestControllerAdvice public class GlobalExceptionHandler { + // InvalidLoginException (로그인 실패) @ExceptionHandler(InvalidLoginException.class) public ResponseEntity> handleInvalidLoginException(InvalidLoginException e) { return ResponseEntity diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/User/filter/SessionCheckFilter.java b/backend/pirocheck/src/main/java/backend/pirocheck/User/filter/SessionCheckFilter.java new file mode 100644 index 0000000..61a09c4 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/User/filter/SessionCheckFilter.java @@ -0,0 +1,40 @@ +package backend.pirocheck.User.filter; + +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.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +public class SessionCheckFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + String path = request.getRequestURI(); + + // 로그인/로그아웃 요청은 세션 체크 제외 + if (path.startsWith("/api/login") || path.startsWith("/api/logout")) { + filterChain.doFilter(request, response); // 다음 필터나 컨트롤러로 넘기는 명령어 + return; // 세션 검사 안함 + } + + HttpSession session = request.getSession(false); // 세션이 없으면 새로 만들지 않고 null을 리턴 (true : 새로 생성) + + if (session == null || session.getAttribute("loginUser") == null) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 설정 + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"success\":false,\"message\":\"세션이 만료되었습니다.\",\"data\":null}"); + return; + } + + filterChain.doFilter(request, response); + + } +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/User/service/UserService.java b/backend/pirocheck/src/main/java/backend/pirocheck/User/service/UserService.java index 04231cf..ff5fb5d 100644 --- a/backend/pirocheck/src/main/java/backend/pirocheck/User/service/UserService.java +++ b/backend/pirocheck/src/main/java/backend/pirocheck/User/service/UserService.java @@ -14,10 +14,10 @@ public class UserService { public User login(String name, String password) { User user = userRepository.findByName(name) - .orElseThrow(() -> new InvalidLoginException("해당 사용자가 존재하지 않습니다.")); + .orElseThrow(() -> new InvalidLoginException("해당 사용자가 존재하지 않습니다.")); //401 if (!user.getPassword().equals(password)) { - throw new InvalidLoginException("비밀번호가 일치하지 않습니다."); + throw new InvalidLoginException("비밀번호가 일치하지 않습니다."); //401 } return user; diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/config/SessionCheckFilterConfig.java b/backend/pirocheck/src/main/java/backend/pirocheck/config/SessionCheckFilterConfig.java new file mode 100644 index 0000000..06e1edf --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/config/SessionCheckFilterConfig.java @@ -0,0 +1,19 @@ +package backend.pirocheck.config; + +import backend.pirocheck.User.filter.SessionCheckFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SessionCheckFilterConfig { + + @Bean + public FilterRegistrationBean sessionCheckFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new SessionCheckFilter()); + registrationBean.addUrlPatterns("/api/*"); + registrationBean.setOrder(1); + return registrationBean; + } +} diff --git a/backend/pirocheck/src/main/java/backend/pirocheck/config/WebConfig.java b/backend/pirocheck/src/main/java/backend/pirocheck/config/WebConfig.java new file mode 100644 index 0000000..a6ae980 --- /dev/null +++ b/backend/pirocheck/src/main/java/backend/pirocheck/config/WebConfig.java @@ -0,0 +1,17 @@ +package backend.pirocheck.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") // 백엔드 API 요청에만 CORS 허용 + .allowedOrigins("http://pirocheck.org:3000") // 프론트 배포 URL + .allowedMethods("GET", "POST", "PUT", "DELETE") // 허용할 HTTP 메서드 + .allowCredentials(true); // 세션 쿠키 주고받기 허용 + } +} diff --git a/backend/pirocheck/src/main/resources/application.yml b/backend/pirocheck/src/main/resources/application.yml index 1416bb4..b8b7096 100644 --- a/backend/pirocheck/src/main/resources/application.yml +++ b/backend/pirocheck/src/main/resources/application.yml @@ -11,4 +11,13 @@ spring: properties: hibernate: format_sql: true - open-in-view: false \ No newline at end of file + open-in-view: false +server: + servlet: + session: + cookie: + http-only: true # 세션 쿠키를 HttpOnly로 설정 (JS에서 접근 불가) + secure: false # HTTPS 전용 전송 (Https -> true로 바꿔야 함) + same-site: Lax # CSRF 방지 + timeout: 30m # 세션 타임아웃 30분 (30 minutes) + address: 0.0.0.0 \ No newline at end of file diff --git a/frontend/src/Deposit.jsx b/frontend/src/Deposit.jsx index ce2198c..500de27 100644 --- a/frontend/src/Deposit.jsx +++ b/frontend/src/Deposit.jsx @@ -5,6 +5,7 @@ import { useEffect, useState } from "react"; const Deposit = () => { const [deposit, setDeposit] = useState(null); + useEffect(() => { const user = JSON.parse(localStorage.getItem("user")); const userId = user?.id; @@ -12,7 +13,9 @@ const Deposit = () => { if (!userId) return; axios - .get(`/api/deposit/${userId}`) + .get(`/api/deposit/${userId}`, { + withCredentials: true, // 세션 쿠키 포함 + }) .then((res) => setDeposit(res.data)) .catch((err) => { alert("보증금 정보를 불러오지 못했습니다.");