Web Storage(LocalStorage, SessionStorage, Cookie) 알아보기

어떤한 임시 값을 저장하기 위해서는 서버의 Session에 저장하거나 Browser(Web Storage)에 저장할 수 있습니다. Session을 사용하지 않는 무상태(Stateless) 웹 애플리케이션에서 임시 값을 저장하기 위해 LocalStorage, SessionStorage, Cookie 등에 저장할 수 있으며 저장되는 데이터는 도메인별로 관리됩니다.

Storage 별 특성

Storage Comments Tab Sharing Limit Security
LocalStorage - key, value는 각 문자에 2바이트를 할당하는 UTF-16 DOMString의 형태로 저장.
- 정수는 자동으로 문자열로 변환.
- 서버에 데이터를 보내려면 헤더 등에 직접 추가해야 함.
- 저장된 데이터는 영구보관 (단, 웨일브라우저는 브라우저 종료 시 삭제)
O 5MB XSS(cross-site scripting) 취약
SessionStorage - key, value는 각 문자에 2바이트를 할당하는 UTF-16 DOMString의 형태로 저장.
- 정수는 자동으로 문자열로 변환.
- 서버에 데이터를 보내려면 헤더 등에 직접 추가해야 함.
- 저장된 데이터는 탭 또는 브라우저 닫을 때 삭제 됨.
X Limited only by system memory XSS(cross-site scripting) 취약
Cookie - 서버가 응답 헤더에 Set-Cookie를 설정하여 생성하거나 javascript에서 직접 생성.
- 유효일자(expires), 만료기간(max-age), 도메인(domain), 경로(path), HttpOnly 등의 속성 및 옵션 제공.
- 서버로 요청을 보낼 때 쿠키가 자동으로 함께 전송되기 때문에 편리하지만 mobile 환경에서 많은 쿠키가 포함되어 있으면 성능이 떨어질 수 있음.
O 약 4KB, 도메인 별 개수 제한있음 브라우저 별 Limits 보기 CSRF(cross-site request forgery) 취약
IndexedDB - Javascript 기반의 객체지향 데이터베이스.
- 많은 양의 구조화된 데이터를 사용해야 될 때 적합.
- key로 인덱싱 된 구조화된 복사 알고리즘을 지원하는 객체를 저장하고 읽을 수 있음.
- 데이터베이스 스키마를 지정하고 연결 후 일련의 트랜잭션을 통해 데이터를 가져오거나 업데이트.
- 작업은 애플리케이션 블록을 방지하기 위해 모두 비동기로 실행됨.
- IndexedDB를 좀 더 쉽게 사용할 수 있도록 하는 localForage, dexie.js, ZangoDB, PouchDB, idb, idb-keyval, JsStore 등의 라이브러리를 사용할 수 있음.
- Internet Explorer에는 지원하지 않는 기능이 있고 지원되지 않는 기능들이 더 있을 것으로 추정됨.
O 브라우저 마다 다름. Chrome의 경우 디스크 용량의 1/6를 앱에서 사용 가능 XSS(cross-site scripting) 취약
Web SQL WebSQL은 지원되지 않는 브라우저들이 있고 W3C는 2010년에 WebSQL 스펙 관리를 중단하였기 때문에 사용하지 않는 것이 좋을 듯 합니다. Web SQL Database - W3C

LocalStorage, SessionStorage 사용 예

localStorage.setItem("name", "kdh");    // 데이터 쓰기
localStorage.getItem("name");           // name 키 값 가져오기 'kdh'
localStorage.setItem("name", "spring"); // 동일한 키로 데이터를 쓰면 덮어씀
localStorage.getItem("name");           // name 키 값 가져오기 'spring'
localStorage.setItem("users", 10);      // 새로운 키에 데이터 쓰기
localStorage.getItem("users");          // '10' 숫자로 넣었지만 문자열로 나옴
localStorage.length                     // 등록된 데이터 수 확인
localStorage.removeItem("name");        // 해당 키 데이터 삭제
localStorage.clear();                   // 전체 삭제

// json 객체 사용하기(숫자형도 사용 가능)
const user = {
  no: 100,
  name:"kdh",
  email:"donghyeok.jh@gmail.com",
  nation:"korea"
}

localStorage.setItem('kdh', JSON.stringify(user)); // 객체를 직렬화하여 저장
localStorage.getItem('kdh');
'{"no":100,"name":"kdh","email":"donghyeok.jh@gmail.com","nation":"korea"}'
JSON.parse(window.localStorage.getItem('kdh'));  // 역질렬화
{no: 100, name: 'kdh', email: 'donghyeok.jh@gmail.com', nation: 'korea'}

// sessionStorage 사용법은 localStorage와 동일합니다.
sessionStorage.setItem("id", "hello");
sessionStorage.getItem("id") // 'hello'
sessionStorage.length //1
sessionStorage.clear()
const cookieName = 'username';
const sessionCookieName = 'username';
const cookieValue = 'kdh cookie';
const expirationDay = 10;
var date = new Date();

// 쿠키 생성
date.setTime(date.getTime() + expirationDay*24*60*60*1000);
document.cookie = encodeURIComponent(cookieName) + '=' + encodeURIComponent(cookieValue) + ';expires=' + date.toUTCString() + ';path=/';

// 세션 쿠키 생성
document.cookie = encodeURIComponent(sessionCookieName) + '=' + encodeURIComponent(cookieValue) + ';path=/';

// 쿠키 값 조회
document.cookie.match('(^|;) ?' + encodeURIComponent(cookieName) + '=([^;]*)(;|$)')[2] // 'kdh'
// 쿠키 삭제
document.cookie = cookieName + '=; expires=Thu, 01 Jan 1999 00:00:10 GMT;path=/';

IndexedDB 사용 예

<html>
<body>
  <input type="button" value="get data" onclick="getData();" />

  <script>
    console.log('IndexedDB Test Start!');

    if (!window.indexedDB) {
      console.log("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
    }

    var db;
    var request = indexedDB.open("MyTestDatabase");

    request.onerror = function (event) {
      console.log("Why didn't you allow my web app to use IndexedDB?!");
    };

    const customerData = [
      { ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" },
      { ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" },
      { ssn: "666-66-6666", name: "kdh", age: 30, email: "kdh@home.org" }
    ];

    request.onsuccess = function (event) {
      db = request.result;
    };

    request.onupgradeneeded = function (event) {
      db = event.target.result;

      // Create an objectStore to hold information about our customers. We're
      // going to use "ssn" as our key path because it's guaranteed to be
      // unique - or at least that's what I was told during the kickoff meeting.
      var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });

      // Create an index to search customers by name. We may have duplicates
      // so we can't use a unique index.
      objectStore.createIndex("name", "name", { unique: false });

      // Create an index to search customers by email. We want to ensure that
      // no two customers have the same email, so use a unique index.
      objectStore.createIndex("email", "email", { unique: true });

      // Use transaction oncomplete to make sure the objectStore creation is
      // finished before adding data into it.
      objectStore.transaction.oncomplete = function (event) {
        // Store values in the newly created objectStore.
        var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
        customerData.forEach(function (customer) {
          customerObjectStore.add(customer);
        });
      };
    };

    function getData() {
      var transaction = db.transaction(["customers"]);
      var objectStore = transaction.objectStore("customers");
      request = objectStore.get("666-66-6666");
      request.onerror = function (event) {
        // Handle errors!
      };
      request.onsuccess = function (event) {
        // Do something with the request.result!
        console.log("Name for SSN 666-66-6666 is " + request.result.name);
      };
    }
  </script>
</body>
</html>

image-20211116170930612

IndexedDB 사용법 상세보기

  • RFC 6265 명세에서 정의되어 있습니다.

  • 쿠키는 브라우저에 저장되는 작은 크기의 문자열로 key=value 형태로 하나의 쌍으로 구성됩니다.

  • 여러개의 key=value;로 구분합니다.

  • document.cookie 프로퍼티를 이용하면 브라우저에서도 쿠키에 접근할 수 있습니다.

  • 옵션

    • path: 접근할 수 있는 URL 경로를 지정하는 것으로 지정된 경로 또는 하위 경로에 있는 페이지만 쿠키에 접근할 수 있습니다. 기본값은 현재 경로

    • domain: 쿠키가 접근 가능한 도메인을 지정합니다. 기본 값은 쿠키를 설정한 도메인에서만 접근할 수 있습니다. 만약 domain을 생략하면 쿠키를 설정한 도메인에서만 접근가능하고 서브 도메인에서는 접근할 수 없습니다.

      // site.com에서만 접근이 가능하고 sub.site.com(서브 도메인)에서는 접근할 수 없습니다.
      document.cookie = "user=kdh"
          
      // 도메인을 설정하면 sub.site.com(서브 도메인)에서도 접근할 수 있습니다.
      document.cookie = "user=kdh; domain=site.com"
      
    • expiresmax-age: expires는 쿠키의 유효 일자를 의미하고 GMT(Greenwich Mean Time) 포맷으로 설정합니다. max-age는 쿠키의 만료기간을 의미하고 초 단위의 값을 설정합니다.

      • max-age <= 0 이면 쿠키가 바로 삭제
      • 두 개의 값 중 하나를 설정하면 브라우저가 종료되어도 삭제되지 않습니다.
      • 만약 하나도 설정하지 않으면 Session Cookie가 되어 브라우저가 종료되면 삭제 됩니다.
      • 만약 두 개 다 설정하면 max-age 기준으로 설정됩니다.
    • secure: 쿠키에 민감한 내용이 포함되어 있어서 HTTPS을 통해서만 서버로 전송되길 원할 때 사용합니다.

      • secure이 설정되지 않은 경우

        https://site.com에서 설정한 쿠키를 http://site.com에서 읽을 수 있습니다. 반대의 경우도 읽을 수 있습니다.

      • secure이 설정된 경우

        https://site.com에서 설정한 쿠키를 http://site.com에서 접근할 수 없습니다.

    • samesite: CSRF(Cross-Site Request Forgery) 공격을 막기 위해 사용 범위를 제한하기 위한 옵션입니다. 일반적으로 CSRF를 막기 위해 애플리케이션에서 CSRF 토큰을 발행/검증하거나 Referer을 체크하는 등의 방법이 있지만 추가적으로 samesite도 같이 적용하여 보안을 강화할 수 있습니다.

      samesite는 3가지 정책이 있고 최신 브라우저 일부는 samesite를 생략하면 lax를 기본 정책으로 사용합니다.

      • samesite=none: 아무것도 제한하지 않습니다. 일부 브라우저에서 none옵션을 사용하기 위해서는 secure 옵션이 강제됩니다.

      • samesite=strict: 크로스 사이트(Cross-site)의 요청은 쿠키가 전송되지 않습니다. 즉 외부사이트에서 img, form, iframe, ajax 등으로 사이트에 요청하면 쿠키가 발행되지 않습니다.

      • samesite=lax: strict 방식에서 예외 사항이 추가된 정책입니다.

        예외 사항
        1. CORS사이트에서 a 태그 사용하여 요청한 경우
        2. CORS사이트에서 form GET Method 사용하여 요청한 경우
        *주의: iframe 안에서 1, 2번으로 요청해도 최상위 레벨 탐색이 아니므로 쿠키가 전송되지 않습니다.
        

    image-20211117164659452

    • httpOnly : javascript 같은 클라인트 측 스크립트가 document.cookie로 쿠키에 접근할 수 없게 합니다. 예를들어 서버에서만 사용하는 token 값이나 session id 값 등은 요청시 자동으로 포함되어 전송되므로 client에서 javascript로 접근할 필요가 없기 때문에 httpOnly 옵션을 사용합니다. 만약 민감한 데이터가 포함된 쿠키에 httpOnly 설정이 되어 있지 않으면 XSS 취약점을 통해 정보가 유출될 수 있습니다.
  • 서드 파티 쿠키(third party cookie) : 방문한 사이트가 아닌 다른 웹사이트에서 발행한 쿠키로 주로 광고 업체에서 접속 사이트에 img 등을 삽입하고 이미지 url 사이트에서 쿠키를 발행합니다. 발행된 쿠키는 사용자를 인식하는데 사용되며 다른 사이트에서도 광고 업체에서 삽입한 img가 있으면 광고 서버에서 사용자가 어떤 사이트에서 어떤 사이트로 이동한지 알 수 있게 됩니다. 하지만 최근에 사파리, 파이어폭스 등 지원을 중단하였고 크롬도 2023년까지 서드 파티 쿠키에 대한 지원을 중단할 것으로 발표하였습니다. 따라서 서드 파티 쿠키는 앞으로 사용되지 않을 쿠키입니다.

참고사이트:

https://ko.javascript.info/cookie

https://han41858.tistory.com/54

https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies

https://developer.mozilla.org/ko/docs/Web/API/IndexedDB_API

https://developer.mozilla.org/ko/docs/Web/API/Window/localStorage

댓글남기기