어느덧 브라우저 시장을 과점하고 있는 크롬은 2020.02.04일 발표된 크롬 80 버전에 새로운 쿠키 정책을 적용했다. 이른바 SameSite라는 속성의 기본 값을 "None"에서 "Lax(느슨한)"로 변경한 것이다. 이는 CSRF(Cross-site request forgery: 사이트 간 요청 위조) 및 의도하지 않은 정보 유출에 대한 취약성에 대처하기 위한 것이다.
이번 포스트에서는 SameSite 속성의 종류와 특징, 설정 방법에 대해 알아보자.
SameSite 속성
SameSite 속성의 정의
SameSite 속성은 HTTP Working Group이 2016에 발표한RFC6265에 포함된 내용으로 쿠키를 자사 및 동일 사이트 컨텍스트로 제한해야 하는지 여부를 선언할 수 있다.
여기서 자사란 브라우저의 주소 표시줄에 표시되는 도메인과 일치하는 쿠키를 자사 쿠키라고 한다.
사이트란 도메인 접미사와 바로 앞의 도메인 부분이 결합된 것을 말한다. 접미사에는 .xyz, .com 처럼 최 상위 도메인 뿐만 아니라 github.io와 같은 서비스 이름도 포함된다. 아래의 예를 살펴보면 쉽게 이해할 수 있다.
test.quietjun.xyz와 work.quietjun.xyz는 same site my-project.github.io와 your-project.github.io는 cross site (github.io는 서비스 이름) src.my-project.github.io와 doc.my-project.github.io는 same site
github.io가 서비스 이름으로 간주되는지 아니면 도메인(github)+접미사(.io)로 되어있는지는 사이트에서 제공하는 문서를 확인해야 한다.
SameSite 속성의 종류
SameSite 속성에는 Strict, Lax, None 3가지 설정이 가능하다.
Strict 모드는 Same Site강의 요청에서만 쿠키의 전송을 허용하며 가장 완벽하지만 편의성이 떨어진다.
Lax 모드는 기본적으로는 Strict이지만 cross site에서의 요청이라도 "safe"한 요청인 Http get 방식, <a href="">, <link rel="prerender">를 통한 접근은 허용한다. 즉 Strict 모드의 팍팍함을 보완해둔 설정이다.
None의 경우 Same Site의 요청은 물론 Cross site의 요청에도 모두 전송을 허용한다. 당연히 보안에 취약해서 지양해야 한다. None을 사용하려면 반드시 HTTPS 프로토콜 하에서 Secure 속성과 함게 사용해야 한다.
주의할 점은 SameSite 속성이 설정되지 않았을 때 크롬 80 버전 이전은 None으로 간주하고 동작했지만 이후는 Lax 라고 명시한 것과 동일하게 동작한다는 점이다.
SameSite의 설정
SameSite가 머에요?
재밋는 점은(사실 재미는 없다.ㅜㅜ) 처음 쿠키가 만들어졌을 때는 SameSite 라는 속성이 없었다는 점이다. 따라서 평소에 사용하던 Cookie 클래스에는 설정하는 방법이 없다.
Set-Cookie Header를 이용한 직접적인 쿠키 생성
Cookie 클래스를 사용하지 않고 직접 쿠키를 생성하기 위해서는 Set-Cookie 헤더에 직접 쿠키를 설정해주는 방법이 있다.(어차피 response를 통해서 add cookie 하는 것도 Set-Cookie 헤더를 이용하게 된다.)
다음은 HttpServletResponse를 이용해서 new Cookie("name","value")에 해당하는 쿠키를 '/' 경로에 세션 쿠키 형태로 추가하면서 SameSite 속성은 Lax로 설정하는 것이다.
// Cookie는 SameSite를 설정할 수 없고 크롬은 Lax로 간주한다.
Cookie c = new Cookie("old", "old");
c.setPath("/");
c.setMaxAge(-1);
res.addCookie(c);
// Header에 문자열로 직접 쿠키를 설정할 수있다.
res.addHeader("Set-Cookie", "name=value; path=/; MaxAge=-1; SameSite=Lax");
// Spring은 ResponseCookie를 이용해서 쿠키를 생성할 수 있다.
ResponseCookie strictCookie = ResponseCookie.from("strict", "strict")
.path("/").sameSite("strict").domain("localhost").build();
res.addHeader("Set-Cookie", strictCookie.toString());
ResponseCookie laxCookie = ResponseCookie.from("Lax", "Lax")
.path("/").sameSite("Lax").domain("localhost").build();
res.addHeader("Set-Cookie", laxCookie.toString());
ResponseCookie noneCookie = ResponseCookie.from("none", "none")
.path("/").sameSite("none").domain("localhost").build();
res.addHeader("Set-Cookie", noneCookie.toString());
다양한 방식과 설정으로 총 5개의 쿠키를 내려보내 주었다.
브라우저에서 확인
위 코드를 동작시킨 후 개발자 도구에서 Application > Storage > Cookies를 살펴보면 아래와 같이 SameSite 속성을 확인할 수 있다.
쿠키별로 SameSite설정이 잘 되어있고 old는 없지만 Lax로 간주되고 있다. 5개의 쿠키 중 None으로 설정한 쿠키가 보이지 않는데 이것을 확인하려면 Network 탭에서 방금 실행한 요청의 Cookies 항목을 살펴보면 된다.
여기서는 none 쿠키를 받았다는 내용이 있기는 한데 경고가 표시되어있다. 경고의 내용은 아래와 같다.
Cookies marked with SameSite=None must also be marked with Secure to allow setting them in a cross-site context. This behavior protects user data from being sent over an insecure connection. Resolve this issue by updating the attributes of the cookie: Specify SameSite=None and Secure if the cookie is intended to be set in cross-site contexts. Note that only cookies sent over HTTPS may use the Secure attribute. Specify SameSite=Strict or SameSite=Lax if the cookie should not be set by cross-site requests.