실무에서 어드민 개발을 진행했던 적이 있다.
이 때 쿠키를 회원의 인증과 인가를 구현하기 위한 수단으로 사용했었다.
로그인 요청이 요청이 오면 그에 맞는 access token과 refresh token을 response body에 포함시켜 반환했고, 그에 맞게 앞단에서 Cookie를 생성한 후에 관리했었다.
만들면서 들었던 생각은 "쿠키를 이렇게 사용하는게 맞아?" 였다.
쿠키에 대해서 잘 모르고 사용하는 것을 직감했었다.
쿠키에 대해서 더 알게 된 내용을 적어 보고자 한다.
HTTP 쿠키(MDN) 글의 쿠키에 대한 정의가 가장 와닿아서 가져와 보았다.
HTTP 쿠키(웹 쿠키, 브라우저 쿠키)는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각입니다. 브라우저는 그 데이터 조각들을 저장해 놓았다가, 동일한 서버에 재 요청 시 저장된 데이터를 함께 전송합니다. 쿠키는 두 요청이 동일한 브라우저에서 들어왔는지 아닌지를 판단할 때 주로 사용합니다. 이를 이용하면 사용자의 로그인 상태를 유지할 수 있습니다. 상태가 없는(stateless) HTTP 프로토콜에서 상태 정보를 기억시켜주기 때문입니다.
MDN의 HTTP 쿠키에 대한 정의를 기반으로 좀 더 구체적으로 봐보자.
MDN의 정의를 다시 보자.
상태가 없는(stateless) HTTP 프로토콜에서 상태 정보를 기억시켜주기 때문입니다.
클라이언트와 서버가 로그인을 하기 위해서 통신을 주고 받는 상황이다. 클라이언트는 id와 password를 POST method를 통해서 서버에 요청했고 서버는 로그인에 성공했다고 응답했다. 하지만 HTTP는 기본적으로 무상태 프로토콜이기 때문에 누가 어떤 요청을 했는지 알 수 없다. 즉, 클라이언트가 전에 로그인을 했다는 사실을 알 수가 없다. 해당 유저가 로그인을 했다와 같은 상태를 기억하고자 할 때 쿠키를 사용하게 된다.
여기서 확인하고 싶은 부분은 정말 쿠키는 Server에서 생성하고 Client에 넘기면 다음 요청에 자동으로 Header에 포함될까? 이다.
정답은 그렇다 이다.
테스트를 할 수 있는 요청을 2가지 준비해 보았다.
@WebServlet(name = "requestCreateCookie", urlPatterns = "/request-create-cookie")
public class RequestCreateCookie extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("cookie-name", "cookie-value");
resp.addCookie(cookie);
resp.getWriter().write("ok");
}
}
@WebServlet(name = "RequestJustGet", urlPatterns = "/request-just-get")
public class RequestJustGet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.getWriter().write("ok");
}
}
우선, /request-create-cookie로 GET 요청을 보내서 쿠키를 생성해보자.

아래와 같이 Http Response Header를 확인해보면
Set-Cookie 속성에 생성한 쿠키가 포함된 걸 확인할 수 있다.
다음 요청으로 /request-just-get로 GET 요청을 보내서 쿠키가 정말 request에 자동으로 포함되어서 전송되는지 확인해보자.

아무 처리 없이 요청을 했는데도 자동으로 Cookie 속성에 포함되어서 요청이 가진 걸 확인할 수 있었다. 재요청을 계속 시도해도 동일하게 Cookie 속성에 포함이 되어서 요청이 가진다.
위에 재요청시에 서버에서 생성한 쿠키가 자동으로 요청헤더에 포함이 되어서 전송된다 라는 쿠키의 특징에 대해서 살펴볼 수 있었다.
그러면 회원의 인증 시, 쿠키에 대해서 클라이언트가 관리할 필요가 있었을까?
지금의 내 지식으로는 클라이언트에서 관리할 필요성이 없다 라고 생각이 든다.
서버에서 쿠키 생성 한 이후에 Header에 설정하면 그걸로 매요청마다 자동으로 request header에 포함되어 넘어올 것이기 때문이다.
우리가 사용하는 쿠키는 어디에 저장될지가 궁금하다. 디스크? 아니면 메모리?

개발자 도구의 Application 탭 -> Cookies에 들어가면 쿠키의 정보를 볼 수 있다.
이 정보는 더 구체적으로는 디스크에 저장되는 걸까? 아니면 메모리 일까?
정답은 Session Cookie는 메모리, Permanent Cookie는 디스크에 저장된다.
Session Cookie가 메모리에 저장되어 있다는 부분은 찾을 수 없었지만,
Permanent Cookie가 디스크에 저장되어 있다는 부분은 직접 확인할 수 있었다.
Mac OS, Chrome 브라우저를 사용하고 있다는 기준으로 아래의 명령어로 들어가서 확인해 보자.
$cd Library/Application Support/Google/Chrome/Default
$vi Cookies

SQLLite foramt으로 작성되어 있는 것을 확인할 수 있다.
위에서 언급한 세션(Session) 쿠키인지 영속(Permanent) 쿠키인지에 따라
저장 공간이 달라진다.왜 달라질까?
두 개의 쿠키는 사라지는 시점이 다르기 때문이다.
세션 쿠키는 브라우저를 껐다가 키면 사라지기 때문에 메모리 공간에 저장되고
영속 쿠키는 만료일을 기준으로 사라지기 때문에 디스크 공간에 저장된다.
@WebServlet(name = "requestTest", urlPatterns = "/request-test")
public class RequestTestServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// Permanent Cookie
Cookie permanentCookie = new Cookie("what", "persistent");
permanentCookie.setMaxAge(1000);
resp.addCookie(permanentCookie);
// Session Cookie
Cookie sessionCookie = new Cookie("what2", "session");
resp.addCookie(sessionCookie);
resp.getWriter().write("ok");
}
}

위에 Cookie의 차이점은 Max-Age 값을 설정했는지 여부에 따라 다르다.
Max-Age값을 설정하면 영속 쿠키, 설정하지 않으면 세션 쿠키로 설정된다.

쿠키는 다양한 옵션을 가지고 있는데 그 중에 주목해야할 옵션은 3가지이다.
일반적으로 인증을 하면 쿠키를 통해 accessToken을 발급 받는다. 이 accessToken 쿠키를 공격자에게 탈취를 당하면 어떻게 될까?🥺
개인 정보를 탈취 뿐만 아니라 가족의 신상 정보까지도 이어질 수 있고 금전적인 손해를 볼 수도 있고 피해 규모는 무궁무진하다. 이와 관련된 옵션들이기 때문에 주의 깊게 볼 필요가 있다.
이 Secure 옵션은 https인 경우에만 cookie를 서버에 전송한다를 의미한다.
기본값이 false로 되어 있기 때문에 Secure 설정을 true로 하는 것을 권장한다.
왜 권장할까?
Https가 아닌 Http 통신일 경우 평문 그대로 서버로 전송되어 중간에 공격자에게 정보를 탈취당할 위험이 다분하기 때문이다.
HttpOnly 옵션은 document.cookies와 같은 Script 태그 내부에서 직접 접근하는 것을 방지하는 옵션이다. 이 옵션 역시 true로 설정하는 것을 권장한다.
왜 이 설정이 중요할까?🤔
XSS(Cross-Site Scripting) 공격을 쿠키에 직접 접근하는 부분에 대해서 방어할 수 있기 때문이다.
XSS란, 웹 애플리케이션에서 발생하는 보안 취약점 중 하나이다.
악의적인 사용자가 웹 페이지에 악성 스크립트를 삽입하여 다른 사용자의 브라우저에서 실행 되게 하는 공격을 의미한다. 이러한 공격은 주로 웹 애플리케이션에서 입력 데이터를 적절하게 필터링하지 않거나 이스케이핑 하지 않을 때 발생한다.
XSS 공격에 대해서 더 자세히 알고 싶다면 나동빈의 XSS 공격의 개요와 실습 관련 영상을 추천한다.☺️
SameSite 옵션은 서드 파티(타사) 쿠키의 보안적 문제를 해결하기 위해 만들어진 옵션이다.
다른 도메인 사이트(Cross-Site)로 전송하는 요청의 경우 SameSite level을 어떻게 설정했는지에 따라 전송에 제한을 두고 있다.
SameSite는 쿠키의 정책으로 None, Lax, Strict 세 가지 중 선택할 수 있다.
크롬에서는 20.02.0 크롬 80버전 부터 SameSite의 기본 값을 None에서 Lax로 변경했다.
왜 SameSite의 정책을 변경했을까?🤔
CSRF 혹은 XSRF(Cross-Site Request Forgery) 문제 때문이다.
CSRF란 서로 다른 사이트간 요청 위조로, 피해자가 사이트 링크를 누르게끔 유도해서 누른 피해자의 쿠키 등의 정보를 탈취하는 공격을 의미한다.
쿠키는 Chrome 브라우저를 제외한 모든 브라우저에서 따로 설정을 하지 않는다면 Cross-Site임에도 기본적으로 전송된다. 그렇기 때문에 Chrome에서는 해당 공격에 대비하고자 SameSite 정책을 변경했다고 할 수 있다.
쿠키에 대해서 제대로 사용하고 싶은 마음에 계속 찾아봤는데 모르고 사용했다는 사실에 반성했다. 다음 auth관련 구현을 할 때 위에 배운 내용을 토대로 조금 더 전문가적으로 세팅할 수 있을 것 같다고 생각했다.