diff --git a/backend/.env.default b/backend/.env.default index b476653..687dd51 100644 --- a/backend/.env.default +++ b/backend/.env.default @@ -77,3 +77,5 @@ CUSTOM_CORS_ALLOWED_ORIGINS=NEED_TO_SET CUSTOM_OAUTH2_REDIRECT_URL=NEED_TO_SET CUSTOM_OAUTH2_FAILURE_URL=NEED_TO_SET CUSTOM_FRONTEND_URL=NEED_TO_SET +PROD_COOKIE_DOMAIN=NEED_TO_SET +DEV_COOKIE_DOMAIN=NEED_TO_SET diff --git a/backend/src/main/java/com/ai/lawyer/global/jwt/CookieUtil.java b/backend/src/main/java/com/ai/lawyer/global/jwt/CookieUtil.java index a498ae6..8de82fd 100644 --- a/backend/src/main/java/com/ai/lawyer/global/jwt/CookieUtil.java +++ b/backend/src/main/java/com/ai/lawyer/global/jwt/CookieUtil.java @@ -3,11 +3,14 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Component; import java.time.Duration; +@Slf4j @Component public class CookieUtil { @@ -18,7 +21,7 @@ public class CookieUtil { // 쿠키 만료 시간 상수 (초 단위) private static final int MINUTES_PER_HOUR = 60; private static final int HOURS_PER_DAY = 24; - private static final int ACCESS_TOKEN_EXPIRE_TIME = 5 * 60; // 5분 (300초) + private static final int ACCESS_TOKEN_EXPIRE_TIME = 60 * 60; // 5분 (300초) private static final int REFRESH_TOKEN_EXPIRE_TIME = 7 * HOURS_PER_DAY * MINUTES_PER_HOUR * 60; // 7일 // 쿠키 보안 설정 상수 @@ -28,6 +31,9 @@ public class CookieUtil { private static final String SAME_SITE = "Lax"; // Lax: 같은 사이트 요청에서 쿠키 전송 허용 private static final int COOKIE_EXPIRE_IMMEDIATELY = 0; + @Value("${custom.cookie.domain:}") + private String cookieDomain; + public void setTokenCookies(HttpServletResponse response, String accessToken, String refreshToken) { setAccessTokenCookie(response, accessToken); setRefreshTokenCookie(response, refreshToken); @@ -52,13 +58,27 @@ public void clearTokenCookies(HttpServletResponse response) { * ResponseCookie를 생성합니다 (SameSite 지원). */ private ResponseCookie createResponseCookie(String name, String value, int maxAge) { - return ResponseCookie.from(name, value) + log.debug("=== 쿠키 생성 중: name={}, cookieDomain='{}', isEmpty={}", + name, cookieDomain, cookieDomain == null || cookieDomain.isEmpty()); + + ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from(name, value) .httpOnly(HTTP_ONLY) .secure(SECURE_IN_PRODUCTION) .path(COOKIE_PATH) .maxAge(Duration.ofSeconds(maxAge)) - .sameSite(SAME_SITE) - .build(); + .sameSite(SAME_SITE); + + // 도메인이 설정되어 있으면 추가 + if (cookieDomain != null && !cookieDomain.isEmpty()) { + log.debug("쿠키 도메인 설정: {}", cookieDomain); + builder.domain(cookieDomain); + } else { + log.debug("쿠키 도메인 설정 안 함 (빈 값 또는 null)"); + } + + ResponseCookie cookie = builder.build(); + log.debug("생성된 쿠키: {}", cookie); + return cookie; } /** diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml index ff9d6f6..efdd512 100644 --- a/backend/src/main/resources/application-dev.yml +++ b/backend/src/main/resources/application-dev.yml @@ -70,3 +70,5 @@ custom: failure-url: ${DEV_OAUTH2_FAILURE_REDIRECT_URL} frontend: url: ${DEV_FRONTEND_URL} + cookie: + domain: ${DEV_COOKIE_DOMAIN} diff --git a/backend/src/main/resources/application-prod.yml b/backend/src/main/resources/application-prod.yml index f9e31f0..0a5af98 100644 --- a/backend/src/main/resources/application-prod.yml +++ b/backend/src/main/resources/application-prod.yml @@ -63,6 +63,8 @@ custom: failure-url: ${PROD_OAUTH2_FAILURE_REDIRECT_URL} frontend: url: ${PROD_FRONTEND_URL} + cookie: + domain: ${PROD_COOKIE_DOMAIN:.trybalaw.com} # 운영환경: 모든 서브도메인에서 쿠키 공유 sentry: dsn: ${PROD_SENTRY_DSN} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index fb9187f..1e2e258 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -155,3 +155,5 @@ custom: failure-url: ${CUSTOM_OAUTH2_FAILURE_URL} frontend: url: ${CUSTOM_FRONTEND_URL} + cookie: + domain: diff --git a/backend/src/test/java/com/ai/lawyer/global/jwt/CookieUtilTest.java b/backend/src/test/java/com/ai/lawyer/global/jwt/CookieUtilTest.java index d5ce7b0..739b637 100644 --- a/backend/src/test/java/com/ai/lawyer/global/jwt/CookieUtilTest.java +++ b/backend/src/test/java/com/ai/lawyer/global/jwt/CookieUtilTest.java @@ -70,7 +70,7 @@ void setTokenCookies_Success() { assertThat(accessCookieHeader).contains(ACCESS_TOKEN_NAME + "=" + ACCESS_TOKEN); assertThat(accessCookieHeader).contains("HttpOnly"); assertThat(accessCookieHeader).contains("Path=/"); - assertThat(accessCookieHeader).contains("Max-Age=300"); // 5분 = 300초 + assertThat(accessCookieHeader).contains("Max-Age=3600"); // 1시간 = 3600초 assertThat(accessCookieHeader).contains("SameSite=Lax"); log.info("액세스 토큰 쿠키 검증 완료: {}", accessCookieHeader); @@ -102,7 +102,7 @@ void setAccessTokenCookie_Success() { String cookieHeader = headerCaptor.getValue(); assertThat(cookieHeader).contains(ACCESS_TOKEN_NAME + "=" + ACCESS_TOKEN); assertThat(cookieHeader).contains("HttpOnly"); - assertThat(cookieHeader).contains("Max-Age=300"); + assertThat(cookieHeader).contains("Max-Age=3600"); assertThat(cookieHeader).contains("SameSite=Lax"); log.info("=== 액세스 토큰 단독 쿠키 설정 테스트 완료 ==="); } @@ -304,11 +304,11 @@ void cookiePathAttribute_Accessibility() { } @Test - @DisplayName("토큰 만료 시간 확인 - 액세스 5분, 리프레시 7일") + @DisplayName("토큰 만료 시간 확인 - 액세스 1시간, 리프레시 7일") void cookieMaxAgeAttribute_ExpiryTime() { // given log.info("=== 토큰 만료 시간 테스트 시작 ==="); - log.info("액세스 토큰 만료: 5분 (300초)"); + log.info("액세스 토큰 만료: 1시간 (3600초)"); log.info("리프레시 토큰 만료: 7일 (604800초)"); // when @@ -321,8 +321,8 @@ void cookieMaxAgeAttribute_ExpiryTime() { var setCookieHeaders = headerCaptor.getAllValues(); String accessHeader = setCookieHeaders.getFirst(); - assertThat(accessHeader).contains("Max-Age=300"); - log.info("액세스 토큰 만료 시간: 300초 (5분)"); + assertThat(accessHeader).contains("Max-Age=3600"); + log.info("액세스 토큰 만료 시간: 3600초 (1시간)"); String refreshHeader = setCookieHeaders.get(1); assertThat(refreshHeader).contains("Max-Age=604800");