JWT를 저장하자! 어디에?

김채은·2022년 5월 5일
0

🗓️ 2022. 04. 25. / 2023. 11. 23 (수정)

사용자 인증을 위해 JWT를 사용하기로 했다. JWT를 사용하기 전에, 웹에서 사용자 인증이 필요할 때 사용하는 기능에 대해 알아보기로 했다.

🐇 세션, 쿠키, 그리고 토큰

사용자 인증에 이런 방식을 사용하는 이유

HTTP는 Stateless 프로토콜이다. 요청끼리의 연결이 없다. 요청이 끝나면 요청을 보낸 게 누군지 잊어버린다. 따라서 요청을 할 때마다 요청을 보내는 사람이 누군지 알려줘야 한다.

쿠키

쿠키는 클라이언트 로컬에 저장되는 키와 값이 들어있는 작은 데이터 파일이다.

쿠키는 웹 서버가 웹 브라우저로 보낸 정보를 저장해뒀다가 서버의 부가적인 요청이 있을 때 다시 보낸다. 사용자가 가지고 다니다가 요청이 있으면 보내주게 된다. 도메인에 따른 제한이 있다. 예를 들어 유튜브가 준 쿠키는 유튜브에게만 보내진다.

쿠키에는 유효기간이 있고, 사용자 인증, 언어 설정 등에 사용될 수 있다.

동작 방식

  1. 클라이언트가 페이지를 요청한다.
  2. 서버에서 쿠키를 생성한다.
  3. HTTP 헤더에 쿠키를 포함시켜 응답한다.
  4. 브라우저가 종료되어도 쿠키 만료 기간이 있다면 클라이언트에서 보관한다.
  5. 같은 요청을 할 경우 HTTP 헤더에 쿠키를 함께 보낸다.
  6. 서버에서 쿠키를 읽어 이전 상태 정보를 변경할 필요가 있을 때 쿠키를 업데이트하여 변경된 쿠키를 HTTP 헤더에 포함시켜 응답한다.

세션

클라이언트를 구분하기 위해 세션 ID를 부여하여, 웹 브라우저가 서버에 접속해서 브라우저를 종료할 때까지 인증상태를 유지한다.

넷플릭스에 여러 명이 로그인하는 것, 인스타그램에 여러 계정을 로그인했다가 특정 계정만 로그아웃 하는 것처럼 인증 정보를 관리할 수 있다.

동작 방식

  1. 클라이언트가 서버에 접속 시 세션 ID를 발급 받는다.
  2. 클라이언트는 세션 ID에 대해 쿠키를 사용해서 저장하고 가지고 있다.
  3. 클라이언트는 서버에 요청할 대 이 쿠키의 세션 ID를 같이 서버에 전달해서 요청한다.
  4. 서버는 세션 ID를 전달받아서 별다른 작업없이 세션 ID로 세션에 있는 클라이언트 정보를 가져와서 사용한다.
  5. 클라이언트 정보를 가지고 서버 요청을 처리하여 클라이언트에게 응답한다.

JWT

  • 토큰 == String

JWT(JSON Web Token)는 모바일이나 웹의 사용자 인증을 위해 사용하는 암호화된 토큰이다. JWT를 사용하면 요청마다 DB를 사용해야 하는 세션의 한계를 극복할 수 있다. 사용자 인증 정보를 서버에 처음 넣어주면 서버는 Signed Info를 뱉는다. 이게 JWT다. 이 다음에 서버를 요청할 땐 이 JWT를 서버로 보내서 해당 토큰이 유효한지만 확인한다.

{헤더}.{페이로드}.{서명}의 구조를 가지고 있다.

규모가 있는 서비스에서 사용하기에는 기능적인 한계가 존재한다. 예를 들어 기기별로 로그인 유지 상태를 관리해야 하는 경우 등이 있다.

🐇 JWT는 어디에 저장해야 할까?

JWT 저장 위치에 대한 고민은 보안 위협에 대비하기 위함이다. 웹에서 어떤 공격이 발생할 수 있는지 먼저 알아보자.

XSS(Cross Site Scripting)

공격자가 미리 XSS 공격에 취약한 웹 사이트를 탐색하고, XSS 공격을 위한 스크립트를 포함한 URL을 사용자에게 노출한다. 공격자가 의도하는 악의적인 Javascript 코드를 웹 브라우저에서 실행하는 것이다.
사용자가 URL을 클릭할 경우 서버에 Request를 전송하고, 서버는 그에 대한 Response를 전송함으로써 정보를 탈취한다.

대응 방법

  • 입력값 제한
    - 사용자의 입력값을 제한하여 스크립트를 삽입하지 못하도록 한다.
  • 입력값 치환
    - 공격이 기본적으로 <script> 태그를 사용하기 때문에 입력시 필터링하고, 스크립트로 해석하지 않도록 치환한다.
  • 스크립트 영역에 출력 자제
    - 이벤트 핸들러 영역에 스크립트가 삽입되는 경우 보호 기법을 우회할 수 있기 때문에, 사용자의 입력을 출력하는 것을 최대한 자제한다.

CSRF(Cross Site Request Forgery)

정상적인 Request를 가로채어 서버에 변조된 Request를 보냄으로써 악의적인 동작을 수행한다.

유저가 img를 클릭하거나 link를 클릭하도록 유도하고, 사용자의 의도와 관계없이 HTTP Request를 전송한다. 유저가 로그인된 상태면 Request가 정상적으로 수행된다. 예를 들어 내 계정을 통해 광고성 게시글이 작성되는 Request가 실행될 수 있다.

대응 방법

  • Referrer 검증
    - 서버에서 Request의 Referrer를 확인하여 도메인이 일치하는지 검증한다.
    • 이 방법으로 대부분의 CSRF 공격을 방지할 수 있다.
    • 하지만 페이지 내 XSS 취약점이 있는 경우, 해당 도메인에서 요청을 보낼 수 있다.
    • 따라서 세밀하게 페이지 단위까지 일치하는지 검증이 필요하다.
  • Security Token 사용
    - 사용자의 세션에 임의의 난수값을 저장하고 사용자의 요청마다 해당 난수값을 포함하여 전송한다.
    • 서버에서 요청을 받을 때마다 토큰이 일치하는지 검증한다.
    • 이 방법도 같은 도메인 내 XSS 취약점이 있으면 CSRF에 취약해진다.

그렇다면 JWT는 어디에 저장해야 할까?

localStorage

✍️ 특징

브라우저에 반영구적으로 저장된다. 브라우저를 종료해도 데이터가 유지되고, 도메인을 기준으로 저장한다. 도메인이 다르면 저장한 데이터를 접근할 수 없다.

👍 장점

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을 사용한다. 다음 포스트에서 계속!

profile
배워서 남주는 개발자 김채은입니다 ( •̀ .̫ •́ )✧

0개의 댓글