쿠키 Samesite 설정으로 이슈 해결하기

2025. 10. 19. 14:10·트러블슈팅

✅ 개요

로컬 개발 환경에서는 프론트엔드와 백엔드 간의 쿠키 전달에 문제가 없었지만, 배포 환경에서는 문제가 발생해 해결 과정을 기록하고자 합니다.

 

✅ 문제 상황

프로젝트에서 다음과 같이 로그인 인증 이후 헤더와 쿠키로 각각 JWT 액세스 토큰과 리프레시 토큰을 전달하고 있습니다.

private TokenResponse getTokenResponse(HttpServletResponse response, Guest guest, String principalName) {
    String accessToken = jwtProvider.generateAccessToken(guest, principalName);
    String refreshToken = jwtProvider.generateRefreshToken(guest, principalName);

    response.addHeader(AUTHORIZATION_HEADER, TOKEN_PREFIX + accessToken);
    Duration refreshDuration = Duration.ofSeconds(jwtProperties.getRefreshToken().getExpiration());

    redisRepository.setValue(String.valueOf(guest.getId()), refreshToken, refreshDuration);

    ResponseCookie cookie = ResponseCookie.from(REFRESH_TOKEN_KEY, refreshToken)
                                          .path("/")
                                          .httpOnly(true)
                                          .maxAge(refreshDuration)
                                          .build();
    response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

    return new TokenResponse(accessToken, refreshToken);
}

그리고 로그아웃 요청에서는 헤더와 쿠키로 두 토큰을 받아서 로그아웃 처리를 하고 있습니다.

@PostMapping("/logout")
public void logout(@RequestHeader(AUTHORIZATION_HEADER) String accessToken,
                   @CookieValue(REFRESH_TOKEN_KEY) String refreshToken,
                   HttpServletResponse response) {
    tokenService.logoutProcess(accessToken, refreshToken);

    ResponseCookie cookie = ResponseCookie.from(REFRESH_TOKEN_KEY, "")
                                          .path("/")
                                          .httpOnly(true)
                                          .maxAge(0)
                                          .build();
    response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

하지만 리프레시 토큰 쿠키가 제대로 전달되지 않는 문제가 발생하였습니다.

 

✅ 원인 분석

쿠키에는 `Samesite` 속성이 있으며, 세 가지 옵션이 존재합니다.

  1. Strict : 동일 사이트에서만 사용 가능
  2. Lax (default) : 동일 사이트에서만 사용 가능 + 요청 및 메서드가 읽기 전용(`GET`, `HEAD` 등)인 경우 크로스 사이트 쿠키 전송 허용
  3. None : 동일 사이트 및 크로스 사이트 쿠키 전송 허용(Secure 설정 필요)

기존 코드의 경우 `Samesite` 속성을 설정하지 않았으므로 Lax가 적용되었습니다. 그리고 로그아웃 API는 POST로 읽기 전용의 안전한 메서드가 아니기 때문에 쿠키가 전달되지 못했던 것이었습니다.

 

 

✅ 해결 방법

해결 방법에는 두 가지가 있습니다.

  1. 로그아웃 API를 GET 메서드로 수정
  2. 쿠키 Samesite 속성을 None으로 설정

1번 방법의 경우 로그아웃 시 서버에서 토큰을 블랙리스트 처리하므로 서버 상태를 변경하는 요청이라고 판단해 적절하지 않다고 생각했습니다. 

2번 방법은 로그아웃 API 메서드를 POST로 유지한 채 안전하게 쿠키를 전달할 수 있으므로 Samesite 설정을 택했습니다.

private TokenResponse getTokenResponse(HttpServletResponse response, Guest guest, String principalName) {
    String accessToken = jwtProvider.generateAccessToken(guest, principalName);
    String refreshToken = jwtProvider.generateRefreshToken(guest, principalName);

    response.addHeader(AUTHORIZATION_HEADER, TOKEN_PREFIX + accessToken);
    Duration refreshDuration = Duration.ofSeconds(jwtProperties.getRefreshToken().getExpiration());

    redisRepository.setValue(String.valueOf(guest.getId()), refreshToken, refreshDuration);

    ResponseCookie cookie = ResponseCookie.from(REFRESH_TOKEN_KEY, refreshToken)
                                          .path("/")
                                          .secure(true)       //추가!
                                          .sameSite("None")   //추가!
                                          .httpOnly(true)
                                          .maxAge(refreshDuration)
                                          .build();
    response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

    return new TokenResponse(accessToken, refreshToken);
}
💡루프백 주소(`localhost`, `127.0.0.1`)는 Secure 설정을 해도 정상적으로 전달이 된다고 합니다.

 

📌 정리

쿠키 설정으로 로컬 환경과 배포 환경의 차이로 인해 발생한 문제를 해결할 수 있었습니다. 안전한 서비스를 구축하는 방법을 한 가지 더 알 수 있게 된 좋은 트러블슈팅이었습니다.

같은 고민을 하고 계시는 다른 분들에게 조금이나마 도움이 되었으면 좋겠고, 더 좋은 의견 주시면 감사하겠습니다.

 

🔖 참고

https://www.codeit.kr/tutorials/94/%EC%BF%A0%ED%82%A4%EC%9D%98%20SameSite%20%EC%98%B5%EC%85%98%EC%9D%B4%EB%9E%80%3F

https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie

 

'트러블슈팅' 카테고리의 다른 글

[Redis] 캐시 스탬피드 현상  (0) 2025.12.14
[Spring WebSocket] STOMP - convertAndSendToUser 알고 사용하기  (0) 2025.11.04
Spring OAuth AccessToken 제대로 추출하기  (0) 2025.10.19
JpaRepository에서 default 키워드를 사용할 때 주의점  (0) 2025.10.19
'트러블슈팅' 카테고리의 다른 글
  • [Redis] 캐시 스탬피드 현상
  • [Spring WebSocket] STOMP - convertAndSendToUser 알고 사용하기
  • Spring OAuth AccessToken 제대로 추출하기
  • JpaRepository에서 default 키워드를 사용할 때 주의점
이런개발
이런개발
geun-00의 흔적 보관소
  • 이런개발
    내일이 기대되는 오늘
    이런개발
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • 백엔드 면접
      • SQL N
        • SUM, MAX, MIN
        • SELECT
        • GROUP BY
        • JOIN
      • Spring
      • JPA
      • 트러블슈팅
      • Infra
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    스프링
    raid
    자바
    티스토리챌린지
    백엔드 면접
    데브코스
    오블완
    JPA
    토스 페이먼츠
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
이런개발
쿠키 Samesite 설정으로 이슈 해결하기
상단으로

티스토리툴바