TIL(Today I Learned)

TIL - 서버에서 보낸 쿠키가 브라우저에 저장되지 않는 문제

Happy._. 2024. 6. 7. 23:07

서버에서 헤더에 AccessToken과 Refresh Token을 담아 프런트로 응답을 보냈을 때 다음과 같이 Response Headers에 Set-Cookie를 보면 정상적으로 응답에 Cookie가 포함된 것을 볼 수 있다.

 

하지만 브라우저에는 해당 Cookie들이 저장되지 않는 문제가 있었다.

Client와 Server 모두 HTTPS를 적용하면 쿠키가 저장되지 않는 문제를 해결할 수 있다는 글을 보고 HTTPS를 적용했다.

 

HTTPS를 적용하기 위해 mkcert 설치를 위한 Scoop 설치(Windows의 경우)

Set-ExecutionPolicy RemoteSigned -scope CurrentUser
irm get.scoop.sh | iex

 

다음 명령어를 입력해 mkcert를 설치한다.

scoop bucket add extras
scoop install mkcert

 

다음 명령어를 입력해 로컬 루트 CA에 mkcert를 추가한다.

인증서 설치 보안 경고가 뜨는 경우 [예]를 누른다.

mkcert -install

 

Server - Spring boot에 HTTPS 적용

터미널에서 다음 명령어를 사용해 key를 발급 받는다.(커스텀 호스트명을 사용하는 경우 localhost 대신 입력)

mkcert -pkcs12 -p12-file keystore.p12 localhost

 

프로젝트 내 resources 폴더에 발급된 key를 넣는다.

application.yml 파일에 다음과 같이 코드를 추가한다.

server:
  port: 443
  ssl:
    key-store-type: PKCS12
    key-store: classpath:keystore.p12
    key-store-password: changeit # default password

 

Client - React에 HTTPS 적용(with Vite)

다음 명령어를 입력해 mkcert 패키지를 설치한다.

 npm i -D vite-plugin-mkcert

 

vite.config.js 내 plugins에 mkcert를 추가한다.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import mkcert from'vite-plugin-mkcert'

export default defineConfig({
  plugins: [react(), mkcert()],
})

 

Server로 요청을 보내는 fetch 코드 예시

credentials: "include"가 포함되어야 함

export const getJwtToken = async (accessToken) => {
  return fetch(`${BASE_URI}/users/signin/kakao?accessToken=${accessToken}`, {
    method: "GET",
    credentials: "include",
  })
    .then((res) => {
      if (!res.ok) {
        const errorMessage = res.json();
        console.log("errorMessage: ", errorMessage);
      }

      return res.json();
    })
    .catch((err) => console.error(err));
};

 

Client로 응답을 보내는 Kotlin 코드 예시

val refreshTokenCookie = Cookie("TODOLIST_REFRESHTOKEN", refreshToken)
refreshTokenCookie.path = "/" // 모든 경로에서, 하위 경로를 지정할 경우 해당 경로의 하위 경로에서만 접근 가능
refreshTokenCookie.maxAge = 60 * 60 * 24 * 30 // 유효기간(초)
refreshTokenCookie.secure = true // 보안 채널(HTTPS)을 통해 전송되는 경우 쿠키 전송(암호화 되지 않은 요청에 쿠키 전달 X)
refreshTokenCookie.isHttpOnly = true // 브라우저에서 쿠키 접근 X(document.cookie X), HTTP 통신으로만 접근

response.status = HttpStatus.OK.value()
response.contentType = MediaType.APPLICATION_JSON_VALUE

response.addCookie(refreshTokenCookie) // 응답 헤더에 Cookie를 포함

// accessToken은 Client LocalStorage에 저장하기 위해 응답 본문으로 보냄
jacksonObjectMapper().writeValue(response.writer, mapOf("accessToken" to accessToken))

 

Client에서 Refresh 요청을 GET으로 하는데 preflight 요청이 발생했는데 그 이유는

  • get요청은 단순요청이라 일반적으로 preflight 요청이 가지 않음
  • Authorization 같은 사용자정의 헤더가 포함되거나 Credentials 설정이 포함된 경우 preflight 요청이 발생

 

 

scoop: https://github.com/ScoopInstaller/Install#readme

mkcert: https://github.com/FiloSottile/mkcert#installation