브라우저Medium#44
localStorage, sessionStorage, Cookie의 차이점을 설명해주세요.
#브라우저#스토리지#쿠키#보안
힌트
지속성, 용량, 서버 전송 여부, 접근 범위를 비교해보세요.
정답 및 해설
localStorage, sessionStorage, Cookie의 차이점을 설명해주세요.
브라우저는 클라이언트 측 데이터를 저장하기 위한 세 가지 주요 메커니즘을 제공합니다. localStorage와 sessionStorage는 Web Storage API의 일부이며, Cookie는 그보다 오래된 HTTP 상태 관리 메커니즘입니다. 세 가지는 저장 용량, 생명 주기, 서버 전송 여부, 보안 설정 측면에서 명확한 차이가 있어 사용 목적에 따라 적절히 선택해야 합니다.
localStorage
localStorage는 브라우저에 영구적으로 데이터를 저장합니다. 사용자가 명시적으로 삭제하거나 코드로 제거하지 않는 한 데이터가 유지됩니다.
특징
- 영구 저장: 브라우저/탭 종료 후에도 유지
- 용량: 약 5~10MB (브라우저마다 다름)
- 범위: 같은 출처(origin = 프로토콜 + 도메인 + 포트)의 모든 탭/창에서 공유
- HTTP 요청 미포함: 서버로 자동 전송되지 않음
- JavaScript로만 접근: 서버 코드에서 직접 접근 불가
- 동기 API: 블로킹 방식으로 동작
// 데이터 저장
localStorage.setItem('theme', 'dark')
localStorage.setItem('userPrefs', JSON.stringify({ lang: 'ko', fontSize: 16 }))
// 데이터 읽기
const theme = localStorage.getItem('theme')
const prefs = JSON.parse(localStorage.getItem('userPrefs'))
// 데이터 삭제
localStorage.removeItem('theme')
// 전체 삭제
localStorage.clear()
// 키 순회
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
console.log(key, localStorage.getItem(key))
}
// 실제 사용 예시 - 사용자 설정 저장
class ThemeManager {
static getTheme(): string {
return localStorage.getItem('theme') || 'light'
}
static setTheme(theme: string): void {
localStorage.setItem('theme', theme)
document.documentElement.setAttribute('data-theme', theme)
}
static toggleTheme(): void {
const current = this.getTheme()
this.setTheme(current === 'light' ? 'dark' : 'light')
}
}
localStorage 주의사항
// ⚠️ Next.js / SSR 환경에서 window 없음 - 가드 필요
const getFromStorage = (key: string): string | null => {
if (typeof window === 'undefined') return null
return localStorage.getItem(key)
}
// ⚠️ JSON 파싱 오류 처리
const getJSON = <T>(key: string): T | null => {
try {
const item = localStorage.getItem(key)
return item ? JSON.parse(item) : null
} catch {
return null
}
}
// ⚠️ storage 이벤트 - 다른 탭에서 변경 감지
window.addEventListener('storage', (event) => {
if (event.key === 'theme') {
console.log('다른 탭에서 테마 변경:', event.newValue)
// UI 업데이트
}
})
// 주의: storage 이벤트는 변경을 발생시킨 탭에서는 발생하지 않음
sessionStorage
sessionStorage는 탭(세션)이 열려있는 동안만 데이터를 유지합니다. 탭을 닫으면 자동으로 데이터가 삭제됩니다.
특징
- 세션 범위 저장: 탭/창을 닫으면 자동 삭제
- 용량: 약 5~10MB
- 범위: 같은 탭 내에서만 공유 (다른 탭과 격리)
- HTTP 요청 미포함: 서버로 자동 전송되지 않음
- 탭 독립성: 같은 URL이라도 탭마다 별도 sessionStorage
// API는 localStorage와 동일
sessionStorage.setItem('cart', JSON.stringify(cartItems))
const cart = JSON.parse(sessionStorage.getItem('cart') || '[]')
sessionStorage.removeItem('cart')
// 실제 사용 예시 - 페이지 간 임시 데이터 전달
class FormWizard {
// 단계별 폼 데이터를 임시 저장
static saveStep(step: number, data: object): void {
sessionStorage.setItem(`wizard_step_${step}`, JSON.stringify(data))
}
static getStep(step: number): object | null {
const data = sessionStorage.getItem(`wizard_step_${step}`)
return data ? JSON.parse(data) : null
}
// 완료 시 모든 임시 데이터 제거
static clearAll(): void {
Object.keys(sessionStorage)
.filter(key => key.startsWith('wizard_step_'))
.forEach(key => sessionStorage.removeItem(key))
}
}
// 실제 사용 예시 - 스크롤 위치 복원
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('scrollPos', String(window.scrollY))
})
window.addEventListener('load', () => {
const savedPos = sessionStorage.getItem('scrollPos')
if (savedPos) {
window.scrollTo(0, parseInt(savedPos))
}
})
Cookie
Cookie는 HTTP 요청 시 자동으로 서버에 전송되는 작은 데이터 조각입니다. 서버가 클라이언트 상태를 인식하기 위해 사용됩니다.
특징
- 만료일 설정:
Expires또는Max-Age로 수명 제어 - 용량: 약 4KB (도메인당 최대 50~300개)
- HTTP 요청에 자동 포함: 같은 도메인의 모든 요청에 Cookie 헤더 첨부
- 서버/클라이언트 모두 접근:
HttpOnly없을 경우 JS에서도 접근 가능 - 보안 옵션:
HttpOnly,Secure,SameSite설정 가능
// JavaScript에서 Cookie 설정 (기본적인 방법)
document.cookie = 'name=value; path=/; max-age=3600; SameSite=Strict'
// Cookie 읽기 (모든 쿠키가 하나의 문자열로 반환됨)
const cookies = document.cookie // "name1=value1; name2=value2"
// Cookie 파싱 유틸리티
function getCookie(name: string): string | null {
const value = `; ${document.cookie}`
const parts = value.split(`; ${name}=`)
if (parts.length === 2) {
return parts.pop()?.split(';').shift() || null
}
return null
}
// Cookie 삭제 - 만료일을 과거로 설정
document.cookie = 'name=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'
Cookie 보안 속성
# 서버에서 Set-Cookie 헤더로 설정
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600; Path=/
# HttpOnly: JavaScript에서 document.cookie로 접근 불가 → XSS 공격 방지
Set-Cookie: authToken=xyz; HttpOnly
# Secure: HTTPS 연결에서만 전송
Set-Cookie: sessionId=abc; Secure
# SameSite: Cross-site 요청 시 쿠키 전송 제어
# Strict: 완전히 동일한 사이트에서만 전송 (가장 강력)
Set-Cookie: csrfToken=def; SameSite=Strict
# Lax: 안전한 HTTP 메서드(GET)의 Top-level navigation에서만 전송
Set-Cookie: sessionId=ghi; SameSite=Lax
# None: 모든 Cross-site 요청에서 전송 (반드시 Secure와 함께)
Set-Cookie: trackingId=jkl; SameSite=None; Secure
Next.js/Node.js에서 Cookie 처리
// Next.js App Router - 서버 컴포넌트에서
import { cookies } from 'next/headers'
async function ServerComponent() {
const cookieStore = cookies()
const token = cookieStore.get('auth-token')
return <div>{token?.value}</div>
}
// Next.js API Route에서 Cookie 설정
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const { username, password } = await request.json()
const token = await authenticate(username, password)
const response = NextResponse.json({ success: true })
// HttpOnly Cookie 설정 (JavaScript에서 접근 불가)
response.cookies.set('auth-token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7, // 7일
path: '/',
})
return response
}
인증 토큰 저장 방법 비교
// ❌ localStorage에 JWT 저장 (일반적이지만 보안 취약)
// XSS 공격으로 탈취 가능
localStorage.setItem('token', jwtToken)
// 이후 모든 요청에 수동으로 헤더 추가 필요
fetch('/api/data', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
})
// ❌ sessionStorage에 JWT 저장
// localStorage보다 약간 안전하지만 XSS에는 여전히 취약
// ✅ HttpOnly Cookie에 JWT 저장 (권장)
// 서버에서 Set-Cookie: token=jwt; HttpOnly; Secure; SameSite=Strict
// JavaScript에서 접근 불가 → XSS 방어
// 자동으로 요청에 포함됨
fetch('/api/data') // 쿠키 자동 전송 (credentials: 'include' 필요할 수 있음)
// CSRF 방어를 위한 Double Submit Cookie 패턴
// HttpOnly Cookie: 실제 인증 토큰 (자동 전송)
// Non-HttpOnly Cookie: CSRF 토큰 (JS에서 읽어서 헤더에 추가)
const csrfToken = getCookie('csrf-token')
fetch('/api/action', {
method: 'POST',
headers: { 'X-CSRF-Token': csrfToken },
})
각 저장소 선택 기준
// localStorage 사용 케이스
localStorage.setItem('theme', 'dark') // UI 설정
localStorage.setItem('language', 'ko') // 언어 설정
localStorage.setItem('recentSearches', '...') // 최근 검색어
localStorage.setItem('dismissedBanner', 'true') // 안내 배너 닫음 상태
// sessionStorage 사용 케이스
sessionStorage.setItem('wizardStep', '2') // 멀티스텝 폼 상태
sessionStorage.setItem('tempCart', '...') // 비로그인 임시 장바구니
sessionStorage.setItem('formDraft', '...') // 작성 중인 폼 임시 저장
// Cookie 사용 케이스
// 서버에서 Set-Cookie로 설정:
// - 인증 토큰 (HttpOnly)
// - 세션 ID
// - CSRF 토큰
// JS에서 document.cookie로 설정:
// - 비로그인 사용자 트래킹 ID
// - A/B 테스트 그룹
정리 표
| 구분 | localStorage | sessionStorage | Cookie |
|---|---|---|---|
| 저장 기간 | 영구 (명시적 삭제 전) | 탭/세션 종료 시 삭제 | 만료일 설정 가능 |
| 저장 용량 | 약 5~10MB | 약 5~10MB | 약 4KB |
| 탭 간 공유 | 동일 origin 모든 탭 | 해당 탭만 | 동일 origin 모든 탭 |
| HTTP 요청 전송 | 미포함 | 미포함 | 자동 포함 |
| JS 접근 | 가능 | 가능 | HttpOnly 없으면 가능 |
| 서버 접근 | 불가 | 불가 | 가능 |
| XSS 방어 | 취약 | 취약 | HttpOnly로 방어 가능 |
| CSRF 방어 | - | - | SameSite로 방어 가능 |
| 주요 용도 | UI 설정, 캐시 | 임시 데이터 | 인증, 세션 관리 |