상황
Jwt 웹 토큰을 사용하면서, RefreshToken은 보안 상의 문제로 Cookie를 통해 프론트 서버로 전송하려고 상황에서 프론트에서 cookie를 사용하지 못하는 에러 상황이 발생했다.
❗ 초기 Cors 설정
registry
.addMapping("/api/**")
.allowedOriginPatterns("*")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true);
Set-Cookie
ResponseCookie refreshTokenCookie = ResponseCookie.from(REFRESH_TOKEN_HEADER, refreshToken)
.httpOnly(true)
.maxAge(refreshExpiredTimeMs)
.build();
httpServletResponse.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
◼ 백엔드 서버에서 위와 같이 Set-Cookie 헤더에 RefreshToken Cookie를 담아서 응답하면, 브라우저에서는 해당 서버로 요청을 보낼 때마다 HTTP 헤더 Cookie 필드에 응답받은 Cookie 값을 같이 보내게 된다.
원인
Same-Site-Policy
2020년부터 크롬은 SameSite 쿠키를 별도로 신설해, 다른 출처로 부터 전송되는 요청에 대해서 쿠키 전송을 제한하도록 했다.
First-Party Cookie vs Third-Party Cookie
◼ 쿠키에 설정된 도메인을 기준으로 First-Party Cookie와 Third-Party Cookie로 나눌 수 있다. 현재 사용자가 접속한 페이지와 쿠키에 설정된 도메인이 일치하는가?
◼ 만일 www.example.com에서 사용자의 요청으로 api.example.com 서버에서 쿠키를 응답 받았다면, 두 도메인이 달라 Third-Party Cookie이다.
SameSite 정책은 다음 3가지 중 하나를 선택할 수 있다. (default: Lax)
✔ None
◼ 다른 출처의 요청(서드 파트 쿠키)에도 쿠키를 사용할 수 있다. 단, Secure 속성을 true로 해야 한다.
만일, SameSite를 None으로 설정해도 Secure 속성을 false로 하면 쿠키를 사용할 수 없다. 즉, https 설정을 해야 한다.
✔ Lax
◼ 대부분의 서드 파티 쿠키를 전송하지 않지만, 예외적인 케이스에서만 허용한다. (ex, 유저가 링크를 누르거나 리다이렉트를 통해 이동하거나 서버의 상태를 바꾸지 않을 것이라 기대하는 GET 요청...)
✔ Strict
◼ 오직 동일 출처의 요청에서 생성된 퍼스트 파티 쿠키만을 전송한다.
📒 해결방안
1. 가장 먼저, 서버에도 https를 적용하기 위해 nginx(프록시 서버)를 사용해서 적용하였다.
2. ResponseCookie 설정
ResponseCookie refreshTokenCookie = ResponseCookie.from(REFRESH_TOKEN_HEADER, refreshToken)
.secure(true)
.httpOnly(true)
.sameSite("None")
.maxAge(refreshExpiredTimeSec)
.build();
httpServletResponse.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
◼ responseCookie에 secure 속성과 sameSite 속성을 추가하였다.
◼ 위와 같이 Set-Cookie에 RefreshToken Cookie가 응답으로 잘 오는 것을 확인할 수 있다.
◼ 위와 같이 Request Header에서도 Cookie에 잘 적용되는 것을 확인할 수 있다.
결론
💥 프론트, 백 서버를 다른 도메인으로 배포하고 Cookie를 사용하기 위해서는 https를 적용하고 sameSite 정책을 고려해서 사용해야 한다.
🛠 추가로, 프론트에서 withCredentials=true를 설정하고 요청해야 Cookie를 사용할 수 있다. 이 부분으로 인해 많은 삽질을 경험하였다...
💬 Credentials 이란?
쿠키, Authorization 인증 헤더, TLS client certificates 를 내포하는 자격 인증 정보를 말한다.
기본적으로 axios 등의 라이브러리를 사용해서 API 요청, 응답을 진행하면, 쿠키와 같은 인증과 관련된 데이터를 함부로 사용하고 받을 수 없다. 이러한 것을 허용해주는 것이 withCredentials=true 옵션이다.
'트러블 슈팅' 카테고리의 다른 글
kotlin + Jpa Lazy Loading 트러블 슈팅 (0) | 2023.06.19 |
---|---|
Jpa Validate 에러 (with. Oracle DB, h2) (0) | 2023.05.30 |
Kotest + 생성자 Bean 주입 (0) | 2023.05.27 |
Kotlin + Spring DTO에서 NotNull 변수의 null Validation (1) | 2023.05.18 |
[Junit5] JPA metamodel must not be empty! (0) | 2022.11.22 |