🗓️ 2022. 04. 25. / 2023. 11. 23 (수정)
사용자 인증을 위해 JWT를 사용하기로 했다. JWT를 사용하기 전에, 웹에서 사용자 인증이 필요할 때 사용하는 기능에 대해 알아보기로 했다.
HTTP는 Stateless 프로토콜이다. 요청끼리의 연결이 없다. 요청이 끝나면 요청을 보낸 게 누군지 잊어버린다. 따라서 요청을 할 때마다 요청을 보내는 사람이 누군지 알려줘야 한다.
쿠키는 클라이언트 로컬에 저장되는 키와 값이 들어있는 작은 데이터 파일이다.
쿠키는 웹 서버가 웹 브라우저로 보낸 정보를 저장해뒀다가 서버의 부가적인 요청이 있을 때 다시 보낸다. 사용자가 가지고 다니다가 요청이 있으면 보내주게 된다. 도메인에 따른 제한이 있다. 예를 들어 유튜브가 준 쿠키는 유튜브에게만 보내진다.
쿠키에는 유효기간이 있고, 사용자 인증, 언어 설정 등에 사용될 수 있다.
클라이언트를 구분하기 위해 세션 ID를 부여하여, 웹 브라우저가 서버에 접속해서 브라우저를 종료할 때까지 인증상태를 유지한다.
넷플릭스에 여러 명이 로그인하는 것, 인스타그램에 여러 계정을 로그인했다가 특정 계정만 로그아웃 하는 것처럼 인증 정보를 관리할 수 있다.
JWT(JSON Web Token)는 모바일이나 웹의 사용자 인증을 위해 사용하는 암호화된 토큰이다. JWT를 사용하면 요청마다 DB를 사용해야 하는 세션의 한계를 극복할 수 있다. 사용자 인증 정보를 서버에 처음 넣어주면 서버는 Signed Info를 뱉는다. 이게 JWT다. 이 다음에 서버를 요청할 땐 이 JWT를 서버로 보내서 해당 토큰이 유효한지만 확인한다.
{헤더}.{페이로드}.{서명}
의 구조를 가지고 있다.
규모가 있는 서비스에서 사용하기에는 기능적인 한계가 존재한다. 예를 들어 기기별로 로그인 유지 상태를 관리해야 하는 경우 등이 있다.
JWT 저장 위치에 대한 고민은 보안 위협에 대비하기 위함이다. 웹에서 어떤 공격이 발생할 수 있는지 먼저 알아보자.
공격자가 미리 XSS 공격에 취약한 웹 사이트를 탐색하고, XSS 공격을 위한 스크립트를 포함한 URL을 사용자에게 노출한다. 공격자가 의도하는 악의적인 Javascript 코드를 웹 브라우저에서 실행하는 것이다.
사용자가 URL을 클릭할 경우 서버에 Request를 전송하고, 서버는 그에 대한 Response를 전송함으로써 정보를 탈취한다.
<script>
태그를 사용하기 때문에 입력시 필터링하고, 스크립트로 해석하지 않도록 치환한다.정상적인 Request를 가로채어 서버에 변조된 Request를 보냄으로써 악의적인 동작을 수행한다.
유저가 img를 클릭하거나 link를 클릭하도록 유도하고, 사용자의 의도와 관계없이 HTTP Request를 전송한다. 유저가 로그인된 상태면 Request가 정상적으로 수행된다. 예를 들어 내 계정을 통해 광고성 게시글이 작성되는 Request가 실행될 수 있다.
그렇다면 JWT는 어디에 저장해야 할까?
✍️ 특징
브라우저에 반영구적으로 저장된다. 브라우저를 종료해도 데이터가 유지되고, 도메인을 기준으로 저장한다. 도메인이 다르면 저장한 데이터를 접근할 수 없다.
👍 장점
CSRF 공격에 비교적 안전하다. 자바스크립트 코드에 의해 헤더에 담기므로 XSS를 뚫지 않는다면 공격자가 사용자인 척 Request를 보내기 어렵다.
👎 단점
XSS 공격에 취약하다. 공격자가 자바스크립트 코드를 주입시켜 쉽게 storage에 접근할 수 있다.
👍 장점
XSS 공격에 비교적 안전하다. HttpOnly
옵션을 사용하면 자바스크립트를 통한 쿠키 접근을 막을 수 있다. 따라서 XSS 공격을 방지할 수 있다. 또, Secure
옵션을 통해 Https로만 쿠키가 통신되도록 하여 보안을 더 높여줄 수 있다.
다만 자바스크립트를 통해 Request를 보낼 수 있으므로, 자동으로 Request에 실리는 쿠키의 특성 상 사용자의 컴퓨터에서 요청을 위조할 수 있다.
👎 단점
CSRF 공격에 취약하다. 자동으로 HTTP Request에 담아서 보내기 때문에 공격자가 Request URL만 알면 관련 링크를 클릭하도록 유도해서 위조하기 쉽다.
최근에는 이러한 취약점을 막기 위해 쿠키에 same-site
속성이 추가되고, JavaScript의 fetch API 속성의 기본값으로 request에 쿠키를 싣지 않음
이 설정돼있다.
Refresh Token을 사용한다. 다음 포스트에서 계속!