🤔 앞서서...
이전 포스팅에서 HTTP의 무상태성 이므로 저장소(로컬/세션 스토리지, 쿠키)가 필요하다고 했는데 이 저장소들은 무엇이고, 어떻게/왜/언제 쓰는지, 각자 장단점은 어떠한지 알아보고자 한다. 이와 더불어 크롬 개발자 도구의 Application탭의 storage와 프론트엔드 전역 상태 관리 라이브러리들은 상태를 어떻게 저장하는지 알아 보도록 한다.
1. HTTP의 무상태성(Stateless)과 비 연결성(Connectionless)
- HTTP는 서버가 클라이언트의 상태를 보존하지 않는 무상태성과 연결을 유지하지 않는 비연결성이라는 특징을 지니고 있다.
- 무상태성 - Stateless
- 장점: 상태를 보관하지 않음으로써 한서버에만 유지되지 않고 다른 서버로부터도 데이터를 받아올 수 있음
- 단점: 상태를 저장하지 않으므로 클라이언트는 추가 데이터를 전송 해야함(인증 정보)
- 비 연결성 - Connectionless
- 장점: 서버와의 연결을 계속 유지하는 방법 대비 서버는 연결을 유지하지 않는 다면 최소한의 자원을 사용하도록 하여 서버를 효율적으로 운용할 수 있다.
- 단점: TCP/IP 연결을 계속 새로 맺어야하고 JS, CSS, Image와 같은 수많은 자원이 함께 다운로드됨 => 자원을 각각 보낼때마다 연결을 끊고 다시 연결하는 것은 비효율 적이므로 연결이 이루어지고 난 뒤 각각의 자원들을 요청하고 모든 응답이 돌아온 후 연결을 종료하는 HTTP 지속 연결(Persistent Connections)로 해결하였다.
- 이러한 무상태성과 비연결성 때문에 클라이언트는 유저정보를 갖고 같은 유저임을 인증받을 정보를 저장하는 저장소가 필요해졌다 => 웹 스토리지, 쿠키에 저장
2. 개발자 도구 애플리케이션탭의 Storage
((크롬기준), 개발자도구-application-storage)
- 종류
- 로컬 스토리지(Local Storage) *
- 세션 스토리지(Session Storage) *
- 쿠키(Cookies) *
- IndexedDB
- 파일이나 블롭 등 많은 양의 구조화된 데이터를 클라이언트에 저장하기 위한 로우 레벨 API
- 웹 스토리지는 적은 양의 데이터를 저장하는데 유용하고, IndexedDB는 많은 양의 구조화된 데이터에 적합하다.(최대저장공간은 사용가능한 디스크 공간의 50%)
- JS 기반의 객체지향 데이터베이스, 인덱스 키를 사용해 저장 및 검색할 수 있음
- DB 오픈 => 객체 저장소 생성 => 트랜잭션 시작 및 DB 작업 요청 => 이벤트 리스너로 요청 완료까지 기다림(비동기) => Do Something
- Web SQL
- IndexDB와 비슷한 많은 양의 구조화된 데이터를 넣기 위한 저장소
- IndexDB와의 차이점은 SQL이라는 이름에서 보이듯 관계형 저장소이다.
- 더이상 사용이 권장되지 않는다
- Trust Tokens
- Interest Groups
- 크로미움의 FLEDGE API(https://developer.chrome.com/ko/docs/privacy-sandbox/fledge/)에 사용되는 저장소
- FLEDGE API는 사용자가 제품이나 서비스를 광고하려는 사이트의 페이지를 방문하면 사이트는 사용자의 브라우저에 특정 기간 동안 특정 관심그룹(Interest Groups)과 사용자를 연결하도록 요청할수 있는데 그 그룹을 저장하는 저장소이다.
- FLEDGE의 작동 방법: 온라인 신발 판매사이트 방문 => Interest Group에 호스트이름(신발 사이트), 입찰 로직, 입찰 신호 액세스 URL 데이터 등록 => 신발 광고를 게재하는 뉴스 사이트 방문 => 광고 슬롯에 들어갈 광고를 선택하기 위해 '경매'를 실행해 낙찰된 광고를 렌더링하여 광고 표시
3. 쿠키
- 주로 서버에서 세팅해주고, 클라이언트는 요청 시 Headers에 전송
- 같은 도메인에서 만들어진 쿠키만 전송하게 됨 => 서버 도메인이 다를 시 서버에서 클라이언트의 쿠키 설정이 되지않는다
- 만료 기간을 설정 가능하다
Expires=<date\> 혹은 Max-Age=<number\>
- 만료기간이 있어서 기간이 끝난다면 삭제되는 영구 쿠키와 만료기간이 없이 브라우저가 종료되면 삭제되는 세션 쿠키로 나뉨
- 문제점
- CSRF(Cross Site Request Forgery): 사용자의 권한을 이용한 공격(비밀번호 변경, 결제 요청)
- SameSite 옵션으로 같은 도메인의 요청에만 쿠키를 전송하도록 한다.
- Referer 검증으로 요청 온 사이트의 도메인을 확인할 수 있다.
- XSS(Cross-Site Scripting): 사용자의 민감한 정보(토큰) 탈취
- HttpOnly 옵션으로 자바스크립트를 막아 해결 가능
- 부족한 저장 용량(4KB)
- 모든 HTTP 요청 헤더에 담겨 전송되므로 불필요한 트래픽이 증가한다.
- 다시보지 않기 팝업 창, n일 동안 보지않기 등으로 쓰인다.
4. 웹 스토리지
- 쿠키의 단점을 보완한 HTML5에 등장한 저장소
- 쿠키의 단점 보완
- 5MB의 큰 저장 용량
- 요청 시 Headers에 전송하지 않아 트래픽 증가 문제 해결
- 문자열만 저장할 수 있는데 JSON형태로 직렬화 할 경우 객체 및 배열 저장 가능
// 잘못 저장하는 예시
localStorage.setItem("obj", {a:1,b:2});
console.log("obj"); // '[object Object]'
// JSON으로 직렬화 하여 저장 하는 예시
localStorage.setItem("JSON", JSON.stringify({key1: '과자', key2: 123}));
console.log(JSON.parse(localStorage.getItem("JSON"))); // parse로 역직렬화
/*
{key1: '과자', key2: 123}
key1: "과자"
key2: 123
[[Prototype]]: Object
*/
- 브라우저를 종료해도 데이터가 유지되는 로컬 스토리지와 브라우저의 탭마다 개별적으로 데이터가 저장되어 탭이 종료 시 삭제되는 세션 스토리지로 나뉜다.
- 주의점: 사파리 시크릿 모드 같은 웹 스토리지가 비활성화 된 경우를 찾아 에러 처리를 해줘야 한다.
function storageAvailable(type) {
let storage;
try {
storage = window[type];
const x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
}
catch (e) {
return e instanceof DOMException && (
// Firefox를 제외한 모든 브라우저
e.code === 22 ||
// Firefox
e.code === 1014 ||
// 코드가 존재하지 않을 수도 있기 떄문에 이름 필드도 확인합니다.
// Firefox를 제외한 모든 브라우저
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// 이미 저장된 것이있는 경우에만 QuotaExceededError를 확인하십시오.
(storage && storage.length !== 0);
}
}
/*
if (storageAvailable('localStorage')) {
// localStorage 사용가능
}
else {
// localStorage 사용불가
}
세션 스토리지는 storageAvailable('sessionStorage')
*/
//
mdn - Web Storage API 사용하기
- 웹 스토리지의 문제점
- XSS - 자바스크립트로 접근 가능함
- 사용자의 입력이 자바스크립트 코드로 실행되지 않도록 innerHTML를 사용하지 않는다면 해결 가능하다.
- 만일 innerHTML를 사용한다면 XSS보안 라이브러리(sanitize-html, DOMPurify)를 사용한다.
- React의 경우에는 기본적으로 막혀 있지만 dangerouslySetInnerHTML 같은 방법으로 넣을 수 있는데 XSS를 막기 위해 사용해선 안된다.
- 독립된 스토리지 - 브라우저/탭(세션 스토리지) 간 공유 불가 ex) 다른 브라우저, 모바일/데스크탑
- 만료 기간 설정 불가
- 동기적으로 실행 되므로 용량이 커지면 저장되는 동안 메인 스레드를 막는다
- 세션 스토리지의 경우에는 이전 페이지 저장, 이전 스크롤 위치 저장, 비로그인 장바구니 등의 저장소로 쓰인다.
- 로컬 스토리지의 경우에는 사용자 설정 저장, 자동 로그인, 글 임시 저장 등의 저장소로 쓰인다.
5. 전역 상태 관리 라이브러리와의 차이점
- 그렇다면 웹 저장소들과 React, Vue 같은 라이브러리에서 쓰이는 전역 상태 관리 라이브러리의 차이점은 뭘까?
- 저장 공간
- 웹 스토리지 와 쿠키: 디스크에 저장 (브라우저가 저장된 폴더의 내부 폴더 안에 저장됨)
- 상태 관리 라이브러리: 메모리에 저장 (RAM에 저장)
- 저장 공간이 다르므로 속도 또한 다르다
- 지속성
- 웹 스토리지 와 쿠키: 브라우저에 계속 남아있고 브라우저/탭 단위로 값을 저장한다.
- 상태 관리 라이브러리: 메모리에 남아 있으므로 휘발되어 지속되지 않고, 새로고침, 페이지 닫기, 태그 변경 등이 있을 때 값이 삭제되거나 삭제되고 다시 생성한다. => 웹 스토리지와 연계해주는 라이브러리(redux-persist, recoil-persist)를 사용하거나 직접 로컬/세션스토리지에 저장하는 함수를 작성하여 해결 가능
- React, Vue와 같은 UI 프레임워크의 상태 관리
- 웹 스토리지는 말그대로 저장소 그 자체이다. 저장만 할 뿐이지 상태관리에서처럼 상태가 바뀌었을 때의 효과 즉 SideEffect가 없다.
- Redux와 MobX, VueX는 Flux 패턴, Recoil은 atom과 selector 등으로 상태를 저장 후 값을 변경하고 React와 Vue 값이 변경되는 것을 참조해 DOM을 리렌더링 한다. 그 에 반해 웹 스토리지와 쿠키로 리렌더링 하기 위해서는 이것만으로 변경이 되지 않고 React와 Vue 안에서 SideEffect를 만들어 주어야 한다.
6. 마치며
- 이전에 dangerouslySetInnerHTML를 사용했던 기억이 있는데 XSS 때문에 사용해서는 안된다.
- 결국 웹스토리지와 쿠키는 각자의 특성을 가진 저장소 이므로 적재적소에 사용해야 한다.
- 평소에도 개발자도구의 application-storage 탭을 자주 사용했는데 웹스토리지와 쿠키 외의 것들이 궁금했었는데, 이번 기회에 전부 구글링하여 정리하니 궁금증이 해결되었고, 사용처를 알았으니 이후에 사용할 기회가 생긴다면 사용할 수 있을 것 같다.
- 예전 프로젝트에서 새로고침을 하면 값이나 컴포넌트가 이상하게 나오곤 했는데, 5번 자료조사에서 전역상태 지속성 이라는 개념을 알았고 전역 상태를 localStorage에 저장해야 한다는걸 깨달았다.
- 개발 환경의 쿠키는 정상적으로 서버에서 잘 저장해줬지만, 배포 환경에서의 쿠키는 정상적으로 저장되지 않았던 이유가 프론트 서버와 백 서버의 도메인이 다르므로 서버 쪽에서 저장이 되지 않았다. 그 당시 CORS에러 인줄 알았으나 쿠키의 특성 때문에 되지 않았던 것이였다. 이 글을 정리하며 당시의 문제 상황을 좀더 정확히 볼수 있었다
참고자료