Home 로컬 환경에서 쿠키 설정이 안되는 문제
Post
Cancel

로컬 환경에서 쿠키 설정이 안되는 문제

배경

백엔드를 배포한 이후, 프론트엔드가 로컬 환경에서 리프레시 토큰이 담긴 쿠키를 설정하지 못하는 문제가 발생했다. 이 문제는 쿠키의 SecureSameSite 설정과 관련이 있었다. 이를 해결한 과정을 정리한다.

slack frontend 프론트엔드 팀원이 슬랙에 남긴 이슈

접근 과정

프론트엔드는 개발 환경에서 localhost를, 백엔드는 운영 환경에서 uosmeeting.uoslife.net을 사용하고 있었다.

  1. 최초 설정

    처음에는 쿠키에 Domain=.uoslife.net을 설정했다. 그러나 프론트엔드가 localhost에서 실행된 경우에는 쿠키가 설정되지 않았다.

    image.png 개발자 도구에서 확인한 응답 헤더의 Set-Cookie

    This Set-Cookie didn’t specify a “SameSite” attribute and was default to “SameSite=Lax,” and was blocked because it came from a cross-site response which was not the response to a top-level navigation. The Set-Cookie had to have been set with “SameSite=None” to enable cross-site usage.

  2. 쿠키 도메인 설정 변경

    이를 해결하기 위해 쿠키의 도메인 값을 localhost로 변경했으나, 이번에는 서버의 도메인(uosmeeting.uoslife.net)과 쿠키의 도메인 설정이 달라서 쿠키가 설정되지 않았다.

    This attemps to set a cookie via a Set-Cookie header was blocked because its Domain attribute was invalid with regards to the current host url.

  3. SameSite=None 설정

    그래서 쿠키의 도메인 값을 다시 .uoslife.net으로 되돌리고, SameSite=None을 설정했다. 하지만 이번에는 “SameSite=NoneSecure 속성과 함께 사용해야 한다”는 에러가 발생했다.

    image.png

    This attempt to set a cookie via a Set-Cookie header was blocked because it had the “SameSite=None” attribute but did not have the “Secure” attibute, which is required in order to use “SameSite=None”.

  4. Secure 설정과 HTTPS

    위 에러는 Chrome에서 SameSite=None이 설정된 경우, Secure=true 설정도 함께 사용해야 하는 제약 때문에 발생한 것이었다. google notice

    When the SameSite=None attribute is present, an additional Secure attribute must be used so cross-site cookies can only be accessed over HTTPS connections.

    이를 해결하기 위해 Secure=true를 설정했다. 그러나 localhostHTTP를 사용하고 있었기 때문에 쿠키를 설정할 수 없었다.

해결 방법

HTTPS 적용

문제를 해결하기 위해 mkcert를 사용하여 로컬 환경에서 사용할 인증서를 생성하고, 프론트엔드 설정 파일에 HTTPS를 적용했다. 이로써 로컬 환경에서도 HTTPS가 적용되어 Secure 쿠키가 정상적으로 설정되었다.

  • vite.config.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    import fs from 'fs';
    
    export default defineConfig({
      plugins: [react()],
      server: {
        https: {
          key: fs.readFileSync('./localhost-key.pem'),
          cert: fs.readFileSync('./localhost.pem'),
        },
      },
    });
    

최종 쿠키 설정

  • .env

    1
    2
    
    COOKIE_DOMAIN=.uoslife.net
    COOKIE_SECURE=true
    
  • CookieUtils.kt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    @Component
    class CookieUtils(
        @Value("\${app.cookie.domain}") private val domain: String,
        @Value("\${app.cookie.secure}") private val secure: Boolean
    ) {
        fun addRefreshTokenCookie(
            response: HttpServletResponse,
            refreshToken: String,
            maxAgeMillisecond: Long
        ) {
            val encodedRefreshToken = URLEncoder.encode(refreshToken, StandardCharsets.UTF_8)
            val cookie =
                ResponseCookie.from("refresh_token", encodedRefreshToken)
                    .domain(domain)
                    .httpOnly(true)
                    .secure(secure)
                    .path("/")
                    .maxAge(maxAgeMillisecond / 1000)
                    .sameSite("None")
                    .build()
            response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString())
        }
    }
    

참고자료

This post is licensed under CC BY 4.0 by the author.

시대팅 매칭 알고리즘: 설계부터 매칭까지

Kotlin object에서 Spring 설정값이 주입되지 않는 문제