이번 s-hook 프로젝트에서 소셜 로그인을 진행하는 과정에서 refreshToken을 보내줄 때 cookie를 활용해서 전송해주었습니다. 이 과정에서 springboot 내 cookie를 활용하는 법에 대한 학습이 필요했고 이를 정리하고자 합니다.
cookie는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터입니다. 브라우저는 쿠키를 저장해 두었다가 그 데이터가 필요한 요청을 보낼 씨 서버에 전송하여 동일한 사용자가 동일한 서버에 요청을 보낸 것인지 알 수 있는 수단입니다. 쿠키를 이용하는 이유는 http 프로토콜의 특징인 stateless를 지키기 위함입니다. 즉, 서버는 이전 요청에 대해 기억하고 있지 않기 때문에 쿠키를 이용하여 이전 요청을 보낸 브라우저라는 것을 알려주는 것입니다.
쿠키는 주로 3가지의 목적으로 이용됩니다.
이 3가지 목적 중에서 1번의 목적인 사용자 식별을 하기 위해 쿠키를 이용하고자 했습니다.
spring에서는 jakarta.servlet.http
에서 Cookie를 객체로 다룰 수 있게 라이브러리를 제공합니다.
이와 같이 쿠키를 생성할 때에는 new Cookie(name, value)
를 통해 선언을 합니다. 이름에서 알 수 있듯이 name에는 쿠키의 이름을 value에는 쿠키에 저장하고자 하는 값을 넣으면 됩니다.
테스트를 통해 쿠키가 올바르게 생성되는지 확인하면 다음과 같습니다.
테스트 결과 설정된 쿠키가 응답 메시지 header의 Set-Cookie 필드로 잘 들어가 있는 것을 확인할 수 있습니다.
쿠키는 보안적으로 취약한 데이터 조각입니다. 통신 과정에서 악성 사용자로부터 쉽게 탈취당할 수 있는데 이를 보안하기 위해 다양한 설정들이 존재합니다. 지금부터 하나씩 알아가보고자 합니다.
먼저 쿠키의 유효기간을 설정하는 방식이 있습니다. 만약 쿠키의 유효기간이 평생인 경우에 이를 악성사용자로 부터 탈취를 당하게 된다면 그 서비스에서 타인의 행세를 할 수 있게 됩니다. 그래서 쿠키를 만드는 경우에는 유효기간을 적절하게 설정하는 것이 중요합니다.
스프링에서는 setMaxAge
메소드를 이용해서 설정이 가능합니다.
위와 같이 7일 동안 쿠키를 유지할 수 있도록 구성을 했고 이를 다시 테스트를 해보면 다음과 같이 쿠키가 구성되는 것을 확인할 수 있습니다.
앞선 쿠키와 다르게 쿠키의 유효기간과 쿠키가 만료되는 요일및 날짜 그리고 시간이 추가되는 것을 확인할 수 있습니다.
서비스에서 쿠키를 보안하지 않고 사용하게 된다면 xss의 공격에 매우 취약한 상태입니다. 예를 들면 다음과 같습니다.
실제 서버를 통해서 이를 확인해보면 다음과 같습니다.
이와 같이 /issue-cookie
에 요청을 보내면 쿠키가 잘 생성된 것을 확인할 수 있습니다. 이 때 브라우저 Console 창에서 쿠키를 조회및 수정을 하는 명령어를 입력하면 cookie값이 조회되고 변조되는 것을 확인할 수 있습니다.
이러면 쿠키 값이 변하게 되어 앞으로의 요청에서는 변경된 쿠키로 요청을 보낼수 있게 되는데 이는 악용의 문제가 발생할 수 있습니다. 이를 보안하기 위해서는 HttpOnly
를 이용합니다.
스프링에서는 setHttpOnly
메소드를 제공하고 이에 대한 설명은 다음과 같습니다
Sets the flag that controls if this cookie will be hidden from scripts on the client side.
script로부터 쿠키를 보호하는 쿠키를 보호하는 설정이라는 것을 알 수 있습니다.
위와 같이 쿠키를 설정하고 다시 서버를 시작해 브라우저 Console 창에서 쿠키를 조회하거나 변경을 시도하더라도 쿠키 값이 조회되지 않을 뿐더러 변경이 되지 않는 것을 확인할 수 있습니다.
samesite 옵션은 크로스 사이트 요청 위조(cross-site request forgery, XSRF)을 막기 위함입니다.
💡 크로스 사이트 요청 위조(cross-site request forgery, XSRF)란?
외부 악성 사이트에서 우리의 서비스에 악의적인 요청을 사용자에게 유도하고 이를 실행했을 때 악의적인 요청이 실행되어 사용자의 정보를 탈취하거나 혹은 악성 사용자에게 송금을 하는 등 악의적인 행위를 하는 공격입니다. 이 때 쿠키가 외부사이트에서 우리 서비스에 요청을 하는데 이용이 되는 상황입니다.
samesite는 3가지의 옵션을 가지고 있습니다. 3가지 옵션을 알아보기 전에 용어 정리를 먼저하겠습니다.
first-party-cookie
: A사이트 브라우저 내 A사이트에서 생성된 쿠키를 의미합니다.
third-party-cookie
: A사이트 브라우저 내 타사이트에서 생성된 쿠키를 의미합니다.
이 옵션은 요청을 보내는 주소와 쿠키를 받는 주소와 같은 경우에만 쿠키를 전송할 수 있도록 하는 옵션입니다. 즉, 악성 사이트의 주소가 [www](http://www.abc.com).악성.com
이고 우리의 서비스 주소가 www.우리 서비스.com
인 경우 사이트 주소가 다르기 때문에 쿠키를 전송하지 않습니다. 즉, first-party-cookie만을 전송하는 것을 허용하는 옵션입니다.
samesite의 기준은 public suffix (com, org, net 등등)가 같은 경우 한단계 하위 단위가 같으면 samesite라고 인식합니다. 예를 들면 www.우리 [서비스.com](http://서비스.com)
인 경우에 public subifx가 com이고 한단계 하위 단위는 우리 서비스
를 비교해서 같으면 samesite로 인식합니다.
기본적으로 strict 옵션과 같이 first-part-cookie만을 허용하지만 예외사항이 존재하는 옵션입니다. 예외사항은 쇼핑몰을 예시로 설명하겠습니다. 쇼핑몰에서 검색을 한 키워드가 쿠키에 담겨지고 쇼핑몰은 이 쿠키를 이용해서 추천 상품을 제공한다는 상황입니다. 이 때 사용자가 다른 브라우저에서 링크를 타고 다시 이 쇼핑몰로 왔을 때 검색 정보를 담고 있는 쿠키를 제공하지 않으면 사용자에게 추천 상품을 제공하지 못하게 됩니다. 이를 해결하기 위해 외부 사이트에서 우리 사이트로 직접 연결되는 경우(ex a테그를 통해 사이트를 직접 넘어가는 경우)에는 쿠키를 전송해주는 것을 허용하는 옵션입니다.
이 속성은 어느 site에서도 쿠키를 전송할 수 있는 방식으로 이는 secure 설정이 필수입니다.
기본적으로 대부분의 최신 브라우저에서는 default 옵션으로 lax 옵션으로 설정합니다.
spring에서 samesite를 설정하는 방식이 2가지가 있습니다. 첫번째 방식은 Configuration을 통해 설정하는 방식입니다.
위와 같이 CookieSameSiteSupplier
를 bean으로 등록해서 이용하는 것으로 생성하는 쿠키에 적용하는 방식입니다. 이는 현재 생성되는 모든 쿠키에서 적용되는 것인데 whenHasName
또는 whenHasNameMatching
으로 특정 쿠키에만 원하는 samesite 옵션을 설정할 수 있습니다.
위와 같이 strict옵션으로 설정을 하고 생성된 쿠키를 확인하면 다음과 같이 samesite 옵션이 잘 설정된 것을 볼 수 있습니다.
두번째 방식은 responseCookie
를 이용하는 것입니다. responseCookie
에 대한 설명은 다음과 같습니다.
An HttpCookie subclass with the additional attributes allowed in the "Set-Cookie" response header. To build an instance use the from static method.
설명을 보면 responseCookie
는 기존의 Cookie와 다르게 다양한 옵션들을 설정할 수 있습니다. 그 예로 samesite 설정이 한번에 가능합니다.
responseCookie의 특징은 위의 코드를 보면 알 수 있듯이 chainning 형식으로 옵션을 설정하기 때문에 가독성이 좋다는 것입니다. 위와 같이 쿠키를 설정을 하고 쿠키를 발급하면 다음과 같이 설정이 잘 되어 있는 것을 알 수 있습니다.
path설정은 발급된 쿠키가 특정 경로(요청)에서만 이용되도록 하는 것입니다. 이를 통해 쿠키가 필요한 요청에 대해서만 쿠키를 이용하도록 설정을 할 수 있습니다. 쿠키의 path를 설정하게 되면 설정된 경로를 포함한 하위 경로에서만 쿠키를 담아서 요청을 보내게 됩니다. 예를 들면 /sample
로 쿠키 path를 설정하게 되면 /sample/**
에 해당하는 모든 요청에는 쿠키를 담아서 요청하고 그 이외에는 쿠키를 담아서 요청을 하지 않습니다.
위와 같이 /find-cookie
로 설정하고 해당 path로 요청을 보내게 되면 요청을 보낼 때 쿠키를 잘 전달하는 것을 볼 수 있습니다.
반면 다른 path(/check-cookie
)로 요청을 보내게 된다면 쿠키를 담아서 요청을 보내지 않습니다.
이와 같이 적절하게 쿠키가 필요한 요청에서만 쿠키를 담아서 보낼 수 있게 path를 설정하는 것이 불필요한 요청에서 쿠키를 탈취당하는 가능성을 낮출 수 있습니다.
쿠키의 Secure 옵션을 설정하면 https 통신에서만 쿠키를 담아서 요청을 보내기 때문에 요청과정에서 쿠키를 탈취 당하더라도 암호화가 되어있게 때문에 악성 사용자가 내부 정보를 가져갈 수 없게 하는 것입니다.
spring에서 cookie의 secure 옵션을 설정하는 방법은 다음과 같습니다.
위와 같이 설정을 하면 https 통신에서만 쿠키를 담아서 요청을 보내게 됩니다. 그럼에도 현재 localhost에서 생성된 쿠키는 localhost으로의 요청(http) 요청에 대해서는 secure 옵션을 설정하더라도 같이 넘어옵니다. 그 이유는 localhost의 경우에는 개발 및 테스트가 이루어지는 곳이기 때문입니다. 따라서 편의를 제공하기 위해서 secure 설정을 하더라도 쿠키가 요청으로 넘어옵니다.
쿠키의 domain을 설정하는 것은 쿠키가 사용되는 도메인을 지정하는 것입니다. 이를 설정해야하는 이유는 하나의 브라우저에서 여러 사이트를 돌아다니게 되면 여러 사이트의 쿠키가 생성되는데 이 때 쿠키의 사용처가 올바르게 설정되지 않으면 불필요한 사이트에서 내 정보를 탈취 당할 수 있기 때문입니다.
기본적으로 cookie를 생성할 때 domain을 설정하지 않으면 쿠키를 발급한 domain으로 설정됩니다. setDomain을 통해서 쿠키를 이용할 수 있는 domain을 지정할 수 있습니다. domain을 지정할 때 .000.com
와 같은 형식으로 하게 되면 000.com
과 더불어 그 하위 도메인(ex dev.000.com
)으로도 쿠키를 이용할 수 있습니다.
spring에서 cookie의 domain 옵션을 설정하는 방식은 다음과 같습니다.
https://web.dev/i18n/ko/samesite-cookies-explained/
https://www.youtube.com/watch?v=Q3YuKipzPbs
https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies