프론트와 백엔드 분리했을 때 쿠키 공유 안되는 이유 (feat. Credentials 옵션)

김유정·2023년 2월 26일
7
post-thumbnail
post-custom-banner

문제 상황

[ 개발 환경 ]
프론트엔드: http://localhost:8081
백엔드: http://localhost:8080

혼자서 프론트엔드와 백엔드를 분리한 상황에서 로그인을 개발하고 있는데, 세션에서 유저 정보를 제대로 가져오지 못하고 계속 새로운 세션을 만드는 문제가 발생했다.

그래서 쿠키를 확인해봤는데, 서버에서 전송한 쿠키가 브라우저에 저장되지 않고 있었다. 위에 첨부한 캡처 사진을 보면, Response Header의 set-cookie에 담긴 세션 아이디 값과 , 브라우저의 저장된 JSESSIONID 값이 다르다.

원래 의도는 아래과 같았지만, 4번부터 제대로 이뤄지고 있지 않은 상황이었다.

  1. [프론트엔드] 구글로부터 엑세스 토큰 받아와서 백엔드로 전달
  2. [백엔드] 받은 엑세스 토큰으로 유저 정보 가져와서 세션에 유저 정보 저장 후
  3. [백엔드] 프론트엔드로 유저 정보와 함께 세션 아이디를 쿠키로 전달
  4. [프론트엔드] 브라우저의 JSESSIONID에 쿠키로 전달받은 아이디값 저장
  5. [프론트엔드] 이후 요청시 쿠키값을 함께 전송
  6. [백엔드] 전달받은 쿠키를 통해 세션에서 저장해놓은 유저 정보 가져오기

해결

원인부터 말하자면, Site는 같지만 Origin이 달라서 쿠키 공유가 안되고 있는 상태였다. 클라이언트와 서버에서 모두 Credentials과 관련된 옵션을 설정하여 해결했다.

클라이언트와 서버의 Site와 Origin이 모두 같을 때는 서버에서 응답 헤더의 set-cookie에 쿠키를 담아 전송하면 브라우저에서 자동으로 쿠키가 저장이 된다. 클라이언트 개발할 때 header에서 set-cookie의 쿠키 값을 추출해서 저장할 필요가 없다는 말이다. 백엔드 개발만 하다보니 이 부분이 헷갈렷다. 따로 저장을 해야하나 싶었는데, 그럼 너무 비효율적일 것 같아서 찾아봤더니 자동으로 세팅이 되는 게 맞았다.

그럼 이제 Site와 Origin은 뭔지, Cross Site나 Cross Origin일 때 쿠키와 관련하여 발생할 수 있는 문제가 뭔지 정리해보자.

Cross Orign vs Cross site


Origin은 Scheme, Host name, Port 조합이며, 이 중 하나라도 다르다면 Cross Origin이다.

Site는 Scheme, TLD(최상위 도메인)와 TLD+1 (최상위 도메인 바로 앞의 도메인)의 조합이며, 이 중 하나라도 다르다면 Cross Site이다. 위의 예시에서는 https://example.com이 Site이다.

이미지 출처: https://web.dev/same-site-same-origin

나의 경우에는 http://localhost:8081 (프론트엔드) 와 http://localhost:8080 (백엔드) 간에 쿠키를 주고받는 과정에서 문제가 생긴 것인데, 포트번호만 다르기 때문에 Cross Origin이지만 Same Site이다.

Cross Origin일 때 발생할 수 있는 문제

브라우저는 Same-Origin-Policy(동일 출처 정책)를 통해 다른 Origin과의 리소스 공유를 제한하여 보안을 위협하는 악의적인 파일이나 사이트로부터 보호할 수 있다. 쿠키는 특히 증명과 관련된 중요한 정보이기 때문에, 함부로 주고받을 수 없도록 제한된다. 따라서 브라우저는 Set-Cookie를 통해 쿠키를 전달받더라도 믿을 수 없는 Origin으로부터 온 것이라면 저장하지 않고, 저장되어 있는 쿠키가 있다고 해도 요청할 때 포함하여 보내지 않는다.
→ 그래서 클라이언트와 서버에서는 증명과 관련된 옵션을 통해 해당 정보를 주고 받겠다고 설정해주어한다.

  • 클라이언트에서는 증명 정보를 포함한 요청을 보내기 위해, withCredentials 옵션을 true로 설정해야한다.
  • 서버에서는 증명 정보를 포함한 요청을 허용하기 위해, Access-Control-Allow-Credentials을 true로 설정해야한다.

Cross Site일 때 발생할 수 있는 문제

악의적인 Site에서 쿠키를 통해 사용자의 중요한 정보에 접근하거나 변경할 수 있기 때문에, Set-Cookie의 SameSite 속성을 통해 쿠키 전송에 제한을 둔다. 최신 버전의 Chrome, Edge, Opera 등의 브라우저에서는 해당 속성의 디폴트 값으로 Lax를 사용한다.
디폴트값을 Lax로 사용하는 브라우저 (링크에서 Defaults to Lax부분 참고):
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#browser_compatibility

  1. Strict: 같은 site의 요청에서만 쿠키의 전송을 허용한다.
  2. Lax: 기본적으로는 Strict와 비슷하지만, Http Get 방식이나 a 태그, link 태그를 통한 접근과 같이 안전한 HTTP 메서드 요청의 경우에는 허용한다. Strict에서 제한이 완화된 옵션이다.
  3. None: 어떤 site이든지 쿠키 전송이 가능하다. 만약 해당 설정으로 세팅하면 쿠키의 Secure 설정을 반드시 세팅해야한다.

Lax의 경우 안전한 HTTP 메서드 요청의 경우에만 쿠키 전송을 허용하기 때문에, 아래 캡쳐 화면과 같이 Set-Cookie에 저러한 메세지가 뜨면서 정상적으로 전송되지 않는다. 따라서 이런 경우 헤더에 SameSite 속성을 변경해주어야한다.

클라이언트에서 withCredentials 옵션 설정하기(feat.Vue.js)

사용하는 메서드나 라이브러리에 따라 다른데, 나는 Vue.js로 프론트 개발을 하고 axios로 HTTP 요청을 보냈기 때문에 해당 방법에 대해 정리하고자 한다. (이 외 다른 메서드나 라이브러리 사용시 아래 링크 참고)
https://inpa.tistory.com/entry/AXIOS-📚-CORS-쿠키-전송withCredentials-옵션
전역으로 설정하기 위해 main.js에 다음과 같은 코드를 추가했다.

import axios from 'axios';

axios.defaults.baseURL = 'http://localhost:8080'; 
axios.defaults.withCredentials = true;

서버에서 Access-Control-Allow-Credentials 옵션 설정하기(feat.Spring)

Spring 프로젝트에서 해당 옵션을 설정하기 위해 설정파일을 추가했다. allowCredentials 메서드를 통해 Access-Control-Allow-Credentials 항목을 true로 설정해야한다.

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:8081")
                .allowCredentials(true);
    }
}

credentials를 포함한 요청에서는 "*"문자를 와일드 카드가 아닌 문자열 자체로 다룬다.
따라서 아래와 같은 응답 헤더 속성을 설정할 때는 허용하길 원하는 값에 대해 명시해줘야한다.

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

참고

post-custom-banner

2개의 댓글

comment-user-thumbnail
2023년 3월 6일

안녕하세요! 지난번에 개발바닥 유튜브 라이브 보다가 알게되었습니다 :)
저도 같은 학교 동문이고, 백엔드 직무를 희망하고 있습니다 ㅎㅎ 반가워서 블로그로 놀러 와봤습니다.포스팅 재미있게 잘 봤습니다!! 저는 velog를 하지 않아서 제 블로그 주소를 남기고 갑니다.ㅎ 앞으로 응원하겠습니다! https://sienna1022.tistory.com/

1개의 답글