어떤 문제가 있었는가?
이럴 거면 왜 RefreshToken을 사용하는 거지?
과거의 로그인 과정에서 로그인을 진행하면, ResponseBody로 AccessToken과, RefreshToken 이 넘어가게 됩니다.
클라이언트는 이를 모두 LocalStorage에 저장해 두고, 필요에 따라 헤더에 추가하는 방식으로 로그인을 진행했습니다
무언가의 이유로 토큰의 탈취가 일어난다면, 같은 저장소에 저장이 되고 있기 때문에, 동시에 탈취가 진행될 수 있었습니다
그렇다면 AccessToken 만을 통해 관리해도 되지 않나? 왜 귀찮게 Refresh 하는 과정이 있는 거지??
내가 해커라면?
1. 로컬 스토리지에 몰래 접근한다
2. AccessToken과 RefreshToken을 모두 가져간다
3. 다른 사람의 역할을 대신 수행한다
RefreshToken까지 동시에 탈취가 되기에, 명시적으로 로그아웃을 진행하지 않는다면, 서버에서 RreshToken을 만료시키기도 힘든 상황이었습니다
이렇게 된다면 당연하지만, 보안에 문제가 생기는 상황입니다
기존의 로그인 방식
사용자가 로그인하게 되면, 서버에서 AccessToken과, RefreshToken을 ResponseBody로 전달해 주고, 이를 이용해 로그인을 진행하게 됩니다
클라이언트는 위 사진의 accessToken을 로컬 스토리지에 넣고, Authorization 헤더에 넣어서 인증을 하게 됩니다
이때 401을 받게 된다면 RefreshToken을 Authorization에 넣고, 토큰 재발급 api를 호출하게 됩니다
재발급된 토큰을 다시 로컬 스토리지에 넣고, 필요에 따라 서버에 전송하게 됩니다
현재는? RefreshToken을 쿠키를 통해 관리하도록 옮기자!
프로젝트를 진행하며 세웠던 전제 조건이 몇 가지 있습니다
1. 클라이언트는 RefreshToken의 존재를 몰라도 된다
그러면 클라이언트에서 로그인 여부는 어떻게 파악하는데?
클라이언트의 입장에서 AccessToken 이 있다면, 로그인이 된 사용자입니다
이 부분은 기존같이 localStorage를 활용해서 검증할 수 있기에, 로그인 여부 파악에는 문제가 없다고 판단하였습니다
그러면 클라이언트에서 토큰 재발급은 어떻게 받는데?
로그인된 사용자의 요청에서 401이 발생한다면, 액세스 토큰이 만료되었다고 가정하고, RefreshToken을 통해서 재발급을 진행하여 재발급을 진행할 수 있습니다
2. RefreshToken과 AccessToken 은 따로 관리되어야 한다
같이 저장되면, 한꺼번에 털릴 수 있다는 부분 때문에, 보안상 좋지 않은 것 같은데요
그러면 어디에 저장되는데?
AccessToken 은 전과 동일하게 ResponseBody를 통해 LocalStorage에 저장되어 헤더에 전송하게 됩니다
RefreshToken 은 쿠키에 저장되어 쿠키를 통해서 요청마다 전달되도록 하였습니다
내 쿠키는 어디 갔지?
처음에는 정말 당당하게 HttpServletResponse 에 있는 cookie를 추가하는 과정만 사용하면 끝날테니 편하겠구나~
라는 생각을 했습니다
httpServletResponse.addCookie(new Cookie("내쿠키", "어디갔지?"));
그래서 위와 같은 코드를 작성했는데요
http://localhost:8080/setCookie 에 접속해서 쿠키가 설정될 줄 알았는데요
setCookie 가 서버에서 오지 않는 현상
왜 setCookie 헤더가 서버에서 오지 않는거지?
Spring을 사용하시고 Cors를 해결해 보신 분들이라면 알 수도 있는데요
CorsConfig 쪽을 보시면 ExposedHeader 부분이 있는데요
이때 응답에 나갈 수 있는 헤더들을 관리하는데, 기본적으로 Set-Cookie 헤더는 등록되어있지 않아서 잘 내보냈지만, 실제로는 가로막혀 나가지 못하는 부분이 생겼습니다
config.addExposedHeader("Set-Cookie");
이때 이를 해결하기 위해 위의 코드를 추가해 주시면, Set-Cookie 헤더가 정상적으로 오는 것을 확인할 수 있습니다
크롬의 개발자도구에서 쿠키를 확인했을 때, 잘 담기는 것을 볼 수 있습니다
혹시 모르니까 리액트로 실행해 보려고 localhost:3000에서 요청을 보냈는데요
여기서부터 쿠키가 설정되지 문제가 생겼습니다
메인 문제 : 쿠키를 왜 설정할 수 없는 거지?
setCookie 헤더는 정상적으로 왔지만, 직접 세팅은 되지 않았습니다
여기서부터 하나하나 디버깅을 해나갔는데요
이 과정에서 디버깅을 더 하기 어렵게 만들었던 부분은
로컬호스트에 포트가 달라도 쿠키가 공유되는 부분인데요
https://lng1982.tistory.com/143
이 부분을 확인해 보시면 실제로 공유가 되는 부분을 확인해 보실 수 있을 것 같아 보여요
현재 프론트와 백엔드의 배포 상황
프론트엔드는 A.com이라는 곳에 배포가 되어있습니다(혹은 localhost:3000)
백엔드는 B.com이라는 곳에 배포가 되어 있습니다
이때 서로 다른 도메인에 배포를 해두었기에, 쿠키 공유 과정에서 어려움을 겪었습니다
퍼스트파티 쿠키와 서드파티 쿠키
퍼스트파티 쿠키
방문한 사이트에서 발행한 쿠키를 의미합니다
현재 A.com에 사용자가 방문해 있기에, A.com에서 쿠키를 발행한다면 퍼스트파티 쿠키가 됩니다
A.com 은 Referer 헤더에 들어가 있기에, Referer 헤더와, 쿠키가 발행된 곳이 동일하다면 퍼스트파티 쿠키가 됩니다
서드파티 쿠키
A.com에 사용자가 방문해 있지만, 백엔드(B.com)에서 쿠키를 발행하면
Referer 헤더는 A.com, 백엔드는 B.com이니, 헤더와 쿠키가 발행된 곳이 다르기에, 서드파티 쿠키가 됩니다
SameSite 옵션
위쪽에 나온 서드파티 쿠키를 어떤 요청까지 포함시키는지를 결정하는 옵션이 SameSite 옵션입니다
퍼스트파티 쿠키는 관계없이 다 전송이 됩니다
SameSite의 종류는 3가지가 있습니다
1. None
SameSite 옵션이 나오기 전 행위와 동일한 역할을 합니다
모든 요청에 서드파티 쿠키와, 퍼스트파티 쿠키가 전송됩니다
2020년도부터 구글 크롬이 Lax를 기본값으로 설정하면서, 여러 문제가 발생했던 원인이기도 합니다
2. Lax
서드 파티 쿠키는 a 태그를 통한 이동이나, window.location 같은 직접 이동에서만 전달이 됩니다
iframe 같은 부분에서는 포함시키지 않는다는 것을 보면 그렇게까지 자유롭게 풀어두지는 않은 것을 볼 수 있습니다
3. Strict
모든 요청에 서드파티쿠키는 전달되지 않습니다
가장 빡빡한 요청 제한이기도 합니다
그렇다면 어떤 옵션을 사용해야 하지?
위쪽에 배포 상황을 요약해 둔 것을 보시면, 홈페이지(A.com)에 접속해 있고, 서버(B.com)에 요청을 보낼 쿠키가 포함되기를 원하는 것이니, SameSite None을 사용해야 합니다
만약 A.com이라는 하나의 도메인을 두고, api.A.com 같은 형태의 서브 도메인을 통해 만들게 된다면, 쿠키의 도메인 속성을 변경해 두게 되면 퍼스트파티쿠키처럼 공유할 수 있습니다
이때 front.A.com에서 api.B.com에서는 도메인 속성을 변경하더라도 서브도메인 간에 공유가 일어나지 않습니다
기본적으로 도메인과, 그 하위도메인에만 서로 공유할 수 있습니다
SameSite를 설정하는 방법
SameSite None 옵션을 사용하려면, Spring을 사용하실 때, Cookie 클래스에서 직접 설정할 수는 없고,
스프링의 ResponseCookie 클래스를 통해 설정할 수 있습니다
아니라면 HttpServletResponse 에다 setHeader를 통해서 직접 설정할 수도 있고요
httpServletResponse.setHeader("Set-Cookie",
"refreshToken=" + refreshToken + "; Path=/; HttpOnly; SameSite=None; Secure; expires=" + date);
SameSite와 Secure 옵션
이때 SameSite None을 사용하려면 Secure 옵션을 사용해야 합니다
Https 요청에만 쿠키를 설정할 수 있도록 하는 것인데요
이는 보안상의 문제로 필수로 되어있다고 합니다
그렇기에 http 서버에만 배포되어 있는 경우에 사용할 수 없습니다
예를 들면 홈페이지(localhost:3000)에서 서버(http://aws.ec2.com)와 통신할 때, Secure 옵션을 사용할 수 없기에, 쿠키를 설정할 수 없습니다
돌아온 내 쿠키 확인하기
직접 B.com에 가서, 개발자도구를 통해 확인할 수 있습니다
A.com에서는 확인할 수 없는데요
A.com에서 B.com 에 대한 쿠키를 볼 수 있다면 보안상으로 매우 심각하게 위협이 될 수 있기 때문입니다
A.com 에서 요청을 보내는 척만 하면 모든 사이트에 있는 쿠키를 다 탈취할 수 있으니까요
간단한 요약
SameStie None을 설정하고, Secure를 적용해서 쿠키를 설정하면 된다
하나의 기술이라도 잘 알고 사용해야겠다는 생각을 다시 한번 하게 되네요
'프로젝트' 카테고리의 다른 글
카페인팀 서버 아키텍처를 설명해드리겠습니다 (7) | 2023.07.14 |
---|---|
프로젝트 git branch 전략 어떤 것이 있을까? (2) | 2023.06.28 |
ElasticCache 에 SpringDataRedis 에서 키 동기화 문제 (0) | 2023.04.30 |
사이드 프로젝트 리팩터링에 관한 이야기 (1) | 2023.04.30 |
이펙티브 기술면접 서비스 회고 (2) | 2023.04.02 |