Cookie, Session, Token, and Web Storage API
Why should not the session ID be stored in sessionStorage?
1. Cookie π©βπ»
1. Cookie
μΏ ν€λ μμ£Ό μμ λ°μ΄ν°λ₯Ό μ μ₯ν μ μλ λΈλΌμ°μ κ° κ°λ μ μ₯ κ³΅κ° μ€ νλλ‘ λμΌν μλ²μ μ μν λ μ μ₯ν μΏ ν€ μ 보λ₯Ό 보λΈλ€.
HTTP protocol μ체λ stateless
μΈλ° μΏ ν€κ° μ 보λ₯Ό μ μ₯νκ³ μλ€ μ μ‘ν¨μΌλ‘μ¨ μ΄λ₯Ό
stateful νκ² λ§λ€μ΄μ£Όλ κ²μ΄λ€.
μΏ ν€λ μ£Όλ‘ λ€μκ³Ό κ°μ μΈ κ°μ§ κΈ°λ₯μ νλ€.
- Session management: λ‘κ·ΈμΈ, μΌν μΉ΄νΈμ κ°μ μλ²κ° μ μ₯ν΄μΌνλ μ 보.
- Personalization: μ¬μ©μ μ νΈ, ν λ§μ κ°μ μ€μ κ°.
- Tracking: μ¬μ©μ νλ λΆμμ μν κΈ°λ‘.
κ³Όκ±°μλ ν΄λΌμ΄μΈνΈμ 무μΈκ° μ μ₯ν νμκ° μμ λ λͺ¨λ κ²μ μΏ ν€μ μ μ₯νλ€. μΏ ν€κ° ν΄λΌμ΄μΈνΈμ μ μ₯ν μ μλ μ μΌν λ°©λ²μ΄μκΈ° λλ¬Έμ΄λ€. νμ§λ§ μ€λλ μ ν΄λΌμ΄μΈνΈμ μ 보 μ μ₯ λͺ©μ μΌλ‘ μΏ ν€λ₯Ό μ¬μ©νλ κ²μ κΆμ₯λμ§ μλλ€. λͺ¨λ μμ²λ§λ€ μΏ ν€κ° ν¨κ» μ μ‘λκΈ° λλ¬Έμ΄λ€. νΉν μ΄κ²μ λͺ¨λ°μΌμμ μ±λ₯μ΄ λ¨μ΄μ§λ μμΈμ΄ λ μ μλ€. λ°λΌμ μΌλ°μ μΈ μ 보 μ μ₯μ΄ λͺ©μ μ΄λΌλ©΄ Modern APIs μΈ Web Storage API (localStorage, sessionStorage) λλ IndexedDB API λ₯Ό μ¬μ©νλ κ²μ΄ κΆμ₯λλ€.
2. JavaScript Access
κ·ΈλΌμλ λΆκ΅¬νκ³ μΏ ν€λ μμ§λ μ¬μ©λκ³ μλ€. μλ²μμ μΈμ μ 보λ₯Ό μ μ‘ν λ μ¬μ©λκΈ°λ νκ³ , μ£Όλ‘ λ§μ΄ μ¬μ©λλ κ³³μ κ΄κ³ μ κ³μμ μλ νν° μΏ ν€κ° μ¬μ©μμ νλμ μΆμ νλ κ²μ΄λ€. λ¬Όλ‘ β¦ μμ¦μ μ΄κ²λ μ νμ ν¬λ‘μ€ μ¬μ΄νΈ μΆμ λ°©μ§μ ꡬκΈμ μλ νν° μΏ ν€ μ°¨λ¨μ μν΄μ λ§μ΄ λ§νμ§λ§ μλ²μμ μΈμ κ΄λ¦¬λ₯Ό μν΄ μΏ ν€μ μ€μ΄ 보λ΄λ κ²μ μ¬μ ν λ§μ΄ μ¬μ©λκ³ μλ€.
μΏ ν€λ λ€μκ³Ό κ°μ νΉμ§μ κ°λλ€.
- λλ©μΈ λ¨μλ‘ μ μ₯
- λΈλΌμ°μ λ§λ€ λ€λ₯΄μ§λ§ νμ€μ κΈ°μ€μΌλ‘ μ¬μ΄νΈλΉ μ΅λ 20κ° λ° 4KBλ‘ μ ν
- μꡬ μ μ₯ λΆκ°λ₯(session, expires, max-age λ¨μλ‘ μ μ₯)
μΏ ν€λ μλ²μμ λ°μ΄ν°λ₯Ό μ€μ΄ 보λ΄λλ° μ¬μ©λκΈ°λ νμ§λ§ λΈλΌμ°μ μμ JavaScript λ₯Ό ν΅ν΄μ μ μ΄νλ κ²λ κ°λ₯νλ€.
document
μλ cookie
λΌλ κ°μ²΄κ° μκ³ , μ¬κΈ°μ μΏ ν€λ₯Ό μ μ₯ν μ μλ€. μΏ ν€μ μ€μ ν μ μλ λνμ μΈ κ°μ λ€μκ³Ό κ°λ€.
- domain: μ ν¨ λλ©μΈ μ€μ
- path: μ ν¨ κ²½λ‘ μ€μ
- expires: λ§λ£ λ μ§(UTC Date) μ€μ
- max-age: λ§λ£ νμ΄λ¨Έ(seconds) μ€μ
μΏ ν€λ μꡬ μ μ₯μ΄ λΆκ°λ₯νλ€κ³ νλ€. expires λλ max-age λ₯Ό μ§μ ν΄μΌνλ©°, μ§μ νμ§ μμ μ κΈ°λ³Έκ°μ session
μΌλ‘ μΈμ
μ΄
μ μ§λλ λμμλ§ μ ν¨νλ€.
μΏ ν€μ λ°μ΄ν°λ₯Ό μ μ₯νλ κ²μ JSON String μ μ¬μ©νλ― λ¬Έμμ΄λ‘ ν μ μμΌλ©°, Syntax 체κ³λ λ³λμ λ°μ΄ν μμ΄ ;
λ‘ κ΅¬λΆνλ€.
document.cookie = 'a=1; domain=localhost;';
document.cookie = 'b=2; domain=127.0.0.1;';
document.cookie = 'c=3; domain=google.com;';
document.cookie = 'd=4; domain=localhost; path=/';
document.cookie = 'e=5; domain=localhost; path=/abc';
document.cookie = 'f=6; domain=localhost; path=/*';
μμ κ°μ΄ μΏ ν€λ₯Ό μ μ₯ν μνμμ localhost/def
λ‘ μ μν΄λ³΄μ.
μ΄ 6κ°μ μΏ ν€λ₯Ό μ μ₯νμ§λ§ 4κ°λ§ μ μ₯λ κ²μ νμΈν μ μλ€. b
μ c
λ λλ©μΈ λΆμΌμΉλ‘ μ μ₯λμ§ μλ κ²μ νμΈν μ μλ€.
μ΄μ νμ domain/path
μμ μ ν¨ν μΏ ν€κ° 무μμΈμ§ μΆλ ₯ν΄λ³΄μ.
console.log(document.cookie)
a=1; d=4
a
μ d
λ§ μ ν¨ν κ²μ μ μ μλ€. e
μ f
λ pathκ° λ¬λΌ νμ¬ μ£Όμμμλ
μ¬μ©ν μ μλ μΏ ν€λ€. μΏ ν€μμμ wildcard λ *
μ΄ μλλΌ /
λ‘ λλ΄λ©΄ μ΄ν μ£Όμλ₯Ό λͺ¨λ ν¬ν¨νλ€λ κ²μ μ μ μλ€.
μ΄λ²μλ localhost/abc
λ‘ μ μν΄ μ ν¨ν μΏ ν€λ₯Ό νμΈν΄λ³΄μ.
e=5; a=1; d=4
μ΄μ e
, a
, d
κ° μ ν¨ν μΏ ν€μμ μ μ μλ€. λ§μ°¬κ°μ§λ‘ localhost/*
λ‘ μ μνλ©΄ f
, a
, d
κ° μ ν¨ν
μΏ ν€κ° λλ€.
μ°Έκ³ λ‘ expires
μ max-age
λ λ€μκ³Ό κ°μ΄ λ±λ‘νλ©΄ μ½κ² μ¬μ©ν μ μλ€.
const THREE_DAYS_SECONDS = 60 * 60 * 24 * 3;
document.cookie = `a=1; max-age=${THREE_DAYS_SECONDS}`;
document.cookie = `b=2; expires=${daysLaterMidnight(3).toUTCString()}`;
function daysLaterMidnight(days) {
const now = new Date();
const threeDaysLaterMidnight = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate() + days
);
return threeDaysLaterMidnight;
}
νΉμ μΏ ν€μ κ°μ κ°μ Έμ€κ±°λ μμ νκΈ° μν΄μλ λ€μκ³Ό κ°μ΄ ν¨μλ₯Ό λ§λ€μ΄ μ¬μ©νλ©΄ νΈλ¦¬νλ€.
export const getCookie = (name) => {
const cookie = document.cookie
.split('; ')
.find((str) => str.split('=')[0] === name);
return cookie ? cookie.split('=')[1] : null;
};
export const getCookies = (...names) => names.map(getCookie);
export const removeCookie = (name) =>
(document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC`);
export const removeCookies = (...names) => names.forEach(removeCookie);
3. HTTP Only & Secure
μμμ μΏ ν€κ° μΈμ
μ κ΄λ¦¬νλ λ° μ¬μ©λλ€κ³ νλ€. Session ID
λ λ§€μ° λ―Όκ°ν κ°μΈμ 보λ€. μ λλ‘ μ μΆμ΄ λμ΄μλ μ λλ€.
κ·Έλ°λ° λ°©κΈ JavaScript λ‘ μΏ ν€λ₯Ό λ무λ μμ½κ² λ€λ£¨λ κ²μ νμΈνλ€.
μΏ ν€μ JavaScript μ μ κ·Όμ μΏ ν€μ λ°μ΄ν°μ HttpOnly
λ₯Ό μΆκ°νλ©΄ JavaScript μ μ κ·Όμ λ§μ μ μλ€. μ΄λ¬λ©΄ λμ΄μ
XSS λ‘ μΈν μΏ ν€ νμ·¨λ₯Ό μ°¨λ¨ν μ μλ€.
μ΄ λ°©λ²μ λΈλΌμ°μ μμμ μΏ ν€ νμ·¨λ λ§μ μ μμ§λ§, μμ΄νμ΄λ₯Ό κ°μ²νκ±°λ μ¬μ§μ΄ ISP μΌμ΄λΈμ κ°μ²νλ λ± λ€νΈμν¬ μ체λ₯Ό
κ°μ²ν΄ μΏ ν€λ₯Ό νμ·¨νλ κ²μ λ§μ μ μλ€. λ¬Όλ‘ , μ΄λ₯Ό μν λμ±
λ μ‘΄μ¬νλ€. λ°λ‘ secure
λ₯Ό μΆκ°νλ κ²μ΄λ€. μ΄ κ°μ΄ μΆκ°λλ©΄
μ΄μ μΏ ν€λ SSL/TLS λ₯Ό μ¬μ©ν λλ§ μ μ‘λλλ‘ μ€μ λμ΄ HTTPS ν΅μ μΌλ‘ μνΈνλ μ μ‘λ§μ νμ©νλ€. κ°λ°μκ° μ€μλ‘
HTTP λ‘ μμ±ν κ²½μ° μΏ ν€μ μ μ‘ μ체λ₯Ό μ°¨λ¨νλ κ²μ΄λ€.
HTTP Only Cookies, Secure Cookies λ λμμ μ€μ ν μ μμΌλ©°, μ΄λ₯Ό ν΅ν΄ λλΆλΆμ μΏ ν€ νμ·¨ λ¬Έμ λ₯Ό ν΄κ²°ν μ μλ€.
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const PORT = 3000;
app.use(cookieParser());
app.get('/', (req, res) => {
res.cookie('myCookie', 'Hello, World!', {
httpOnly: true,
secure: true
});
res.send('Cookie with HttpOnly and Secure flag has been set.');
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
2. Session π©βπ»
1. Session
μΏ ν€μ μ©λ μ€ νλκ° μΈμ μ 보λ₯Ό μ μ‘ν λ μ¬μ©νλ€κ³ νλ€. μΈμ μΌλ 무μμΌκΉ? HTTP κ° stateless μ΄κΈ° λλ¬Έμ μ¬μ©μμ μΈμ¦ μ 보λ₯Ό μ μ§νκΈ° μν΄μ μ΄ μνλ₯Ό μ μ₯ν νμκ° μλ€. μ΄ λ°©λ² μ€ νλκ° λ°λ‘ μΈμ μ΄λ€.
μΈμ μ΄ λ¬Όλ‘ μμ§λ μ¬μ©λκ³ λ μμ§λ§ μ μ ν μ΄μΌκΈ° ν Token μ λΉνλ©΄ μ’ λ μ ν΅μ μΈ λ°©μμ΄λΌ ν μ μλ€.
- ν΄λΌμ΄μΈνΈκ° μλ²μ λ‘κ·ΈμΈ μμ².
- μλ²λ λ‘κ·ΈμΈμ μ²λ¦¬(authentication), μΈμ μ DBμ μ μ₯νκ³ Session ID κ° λ΄κΈ΄ μΏ ν€λ₯Ό ν΄λΌμ΄μΈνΈμ λ°ν.
- ν΄λΌμ΄μΈνΈλ μΏ ν€μ ν¨κ» μλ²μ νμν μμ μ μμ².
- μλ²λ μΏ ν€μ Session ID λ₯Ό μ μ₯ν DB μ λΉκ΅ν΄ μ ν¨νμ§ κ²μ¦. μ ν¨ν μΈμ μ΄ νμΈλλ©΄ μ¬μ©μ μμ²μ μ²λ¦¬ν΄ μλ΅.
2. Limitation of Session
μΈμ μ μλ²κ° μ μ₯νκ³ κ΄λ¦¬νκΈ° λλ¬Έμ ν΄λΌμ΄μΈνΈμ λ€νΈμν¬ κ³Όμ μμ νμ·¨λ‘ μΈν XSS, CSRF 곡격μ λ°μ μ μλ€λ κ²λ§ μ‘°μ¬νλ©΄ μμ μ±μ΄ λ§€μ° λλ€. νμ§λ§ μμ¦μ JWT, OAuth κ°μ Token μ΄ λ§μ΄ μΈκΈλκ³ μ¬μ©λλ€. μ μ€λ«λμ μ μ¬μ©νκ³ , μ¬μ ν λ§μ κ³³μ μ¬μ©μ€μΈ μΈμ μ μλΉμλ ν ν°μ λ체λλ κ²μΌκΉ?
μ΄λ νμ¬μ μλΉμ€κ° κΈ°λ₯μ΄ λ§μμ Έ μλ²κ° μμ©ν μ μλ μ²λ¦¬λμ νκ³κ° λμ΄μλ©΄ μ΄λ»κ² ν΄μΌν κΉ? λ λμ μ»΄ν¨ν νμκ° νμνλ€. νμ¬μ μλΉμ€κ° μ νλ € μ¬μ©μκ° λ§μμ Έ μ²λ¦¬λμ νκ³λ₯Ό λμ΄μλ©΄ μ΄λ»κ² ν΄μΌν κΉ? λ§μ°¬κ°μ§λ‘ λ λμ ν ν¨ν νμκ° νμνλ€.
λ λμ μ»΄ν¨ν μ±λ₯μ μ»κΈ° μν΄μλ μ΄λ»κ² ν΄μΌν κΉ? μ°μ κ°μ₯ μ¬μ΄ λ°©λ²μ μλ²μ μ±λ₯μ λμ΄λ κ²μ΄λ€. κ³ μ±λ₯ μλ²λ‘ κ΅μ²΄νλ€κ±°λ AWS μ κ°μ ν΄λΌμ°λλ₯Ό μ¬μ©νλ€λ©΄ λ λμ μ»΄ν¨ν μ±λ₯μΌλ‘ μ κ·Έλ μ΄λ μμ²μ νλ κ²μ΄λ€. μ΄λ° νμ₯ λ°©λ²μ Scare-Up μ΄λΌ νλ€.
νμ§λ§ λ¬Έμ λ λ¨μΌ μ»΄ν¨ν°μ μ±λ₯μ λμ΄λ κ²μ λ€μκ³Ό κ°μ νκ³λ₯Ό κ°λλ€.
- μ±λ₯μ΄ λμμ§μλ‘ ν₯μν λλΉ λΉμ© μ¦κ°κ° ν¨μ¬ ν¬λ€.
- λ¨μΌ μ»΄ν¨ν°μ μ±λ₯μ νκ³κ° μ‘΄μ¬νλ€.
- μλ²κ° λ€μ΄λλ©΄ μ 체 μλΉμ€κ° μ£½λλ€.
κ·Έλ¬λ©΄ μ΄λ»κ² ν΄μΌ λΉμ©λ μ€μ΄κ³ , μ»΄ν¨ν μ±λ₯μ νκ³λ 극볡νλ©°, μλΉμ€κ° μ€λ¨λλ μ΅μ μ μ¬νλ₯Ό λ§μ μ μμκΉ?
λΆμ° μ»΄ν¨ν μ μ¬μ©νλ κ²μ΄λ€. μμ¦ λ°±μλμμ λ§μ΄ μ¬μ©νλ MSA(Microservice Architecture) μμ λΆμ° μ»΄ν¨ν μ νμ©ν κ²μ΄λΌ ν μ μλ€.
λ‘λ λ°Έλ°μλ₯Ό Proxy λ‘ λκ³ λ€μ μ¬λ¬ κ°μ μλ²κ° μ‘΄μ¬νλ©΄ μΈν°λ· μμμλ νλμ μλ²μ²λΌ 보μ΄μ§λ§ μ¬λ¬ κ°μ μλ²κ° μν μ λΆλ΄ν μ μκ² λλ€. λΉμ© λλΉ μ±λ₯ μ¦κ°κ° ν¨μ¨μ μ΄λ©°, μλ²λ₯Ό μΆκ°ν¨μ λ°λΌ μ±λ₯μ΄ κ±°μ μ λΉλ‘νκ² μ€λ₯Έλ€. λν μ¬λ¬ μλ²κ° μ‘΄μ¬νκΈ° λλ¬Έμ νλμ μλ²κ° κ³ΌλΆνλ‘ λ€μ΄λλλΌλ λ€λ₯Έ μλ²κ° μκΈ° λλ¬Έμ μ 체 μλΉμ€κ° μ£½μ§ μλλ€. μμ μλΉμ€ λ³λ‘ λΆμ°νμ κ²½μ° νΉμ μλΉμ€λ§ μ£½κ±°λ, λμΌν μλ²λ₯Ό μ¬λ¬ κ° λΆμ°νμ κ²½μ° λ€μ΄λ μλ²κ° μ¬λΆν λκ³ μ μν λλ λμλ§ λ€λ₯Έ μλ²κ° λ²ν ¨μ£Όλ©΄ λλ€. λν μ€μ μλ²λ₯Ό μΈλΆ λ€νΈμν¬μ λ ΈμΆμν€μ§ μμμΌλ‘μ¨ λ§μ€νΉ ν¨κ³Όλ₯Ό μΆκ°ν΄ 보μμ΄ κ°νλλ κ²μ λ€μΌλ‘ μ»κ² λλ ν¨κ³Όλ€. μ΄λ° νμ₯ λ°©λ²μ Scale-Out μ΄λΌ νλ€.
μ΄λ»κ² λ‘λλ°Έλ°μκ° ν΄λΌμ΄μΈνΈλ₯Ό λΆμ°μν€λμ§, λ‘λλ°Έλ°μ± ν¨κ³Όλ₯Ό λμ΄κΈ° μν΄ μ΄λ»κ² ν΄μΌνλμ§μ κ°μ λ¬Έμ λ 무μνκ³ μΈμ
μ
λν΄μλ§ μ΄μΌκΈ°ν΄λ³΄μ.
μΈμ
μ μλ²μμ κ΄λ¦¬νλ€κ³ νλ€. κ·Έλ λ€λ©΄ ν΄λΌμ΄μΈνΈμ μΈμ
μ΄ μ μ§κ° λκΈ° μν΄μλ μλ²κ° νμ ν΄λΌμ΄μΈνΈμ μΈμ
μ 보λ₯Ό μκ³ μμ΄μΌ
νλ€λ λ¬Έμ κ° μκΈ΄λ€. μ΄λ₯Ό ν΄κ²°νκΈ° μν λ°©λ²μλ μ΄λ€ κ²μ΄ μμκΉ?
1 ) Sticky Session
첫째, λ‘λλ°Έλ°μκ° ν΄λΌμ΄μΈνΈμ μ 보λ₯Ό κΈ°μ΅ν΄ λ§€λ² λμΌν μλ²λ‘ μ°κ²°μν€λ κ²μ΄λ€. νμ§λ§ μ΄ λ°©λ²μ κ²°κ΅ λ‘λλ°Έλ°μμ λΆλ΄μ΄ 컀μ§κ² λλ€. μλ²μ λΆλ΄μ μ€μ΄κ³ μ λΆμ° μλ²λ₯Ό λμ νκ³ λ‘λλ°Έλ°μλ₯Ό λμλλ° μ΄μ λ‘λλ°Έλ°μλ₯Ό νμ₯ν΄μΌνλ μν©μ΄ λμ΄λ²λ¦° κ²μ΄λ€.
λμ§Έ, μΏ ν€μ μΈμ μ μλ΅ν λ μ΄λ€ μλ²μ μ°κ²°λμλμ§ μ 보λ₯Ό μΆκ°ν΄ λ‘λλ°Έλ°μκ° μ΄λ₯Ό νμΈνκ³ λμΌν μλ²λ‘ μ°κ²°μν€λ κ²μ΄λ€. λ‘λλ°Έλ°μκ° μ°κ²°ν μλ²λ₯Ό κΈ°μ΅ν νμ μμ΄ μΏ ν€λ₯Ό νμΈλ§ νλ©΄ λλ λΆλ΄μ΄ μ€μ΄λ λ€. νμ§λ§ μλ² λ§μ€νΉ ν¨κ³Όκ° μ¬λΌμ§λ€.
κ·Έλ¦¬κ³ μ΄λ€ λ°©λ²μ μ¬μ©νλ μ°κ²°λ μλ²κ° λ€μ΄λ κ²½μ° μλΉμ€κ° μ£½μ§λ μμ§λ§ ν΄λΌμ΄μΈνΈλ μΈμ μ μμ΄λ²λ¦¬κ² λλ λ¬Έμ κ° λ°μνλ€.
2 ) Session Clustering
μλ²μ μΈμ μ΄ μμ±, λ³κ²½, μ κ±°λ λ λ€λ₯Έ μλ²μ μ νν΄ λͺ¨λ μλ²κ° λμΌν μΈμ μ μμ νλ κ²μ΄λ€. μ΄ λ°©λ²μ μ Sticky Session μ΄ κ°λ λͺ¨λ λ¬Έμ λ₯Ό ν΄κ²°νμ§λ§ λ€λ₯Έ λ¬Έμ κ° λ°μνλ€.
λΆμ° μλ²κ° μ¬λ¬ λμΌ κ²½μ° λͺ¨λ μλ²μ μΈμ μ μ νν΄μΌνλ―λ‘ λ€νΈμν¬ λΉμ©μ΄ μ¦κ°νλ€. μ¬μ©μ μ¦κ°λ‘ μΈν μλ² λΆλ΄μ μ€μ΄κΈ° μν΄ λΆμ° μλ²λ₯Ό μ¬μ©νμ§λ§ μΈμ μ κ²°κ΅ Scale-Up μ νμ λμ λ¬λΌμ§ κ²μ΄ μλ€. λͺ¨λ μλ²κ° λμΌνκ² λͺ¨λ μΈμ μ κ°μ ΈμΌνλ€. μ΄λ κ²°κ΅ μΈμ μΌλ‘ μΈν μλ² λΆλ΄μ μ¦κ°μν€λ λ¬Έμ λ₯Ό λ°μμν¨λ€. λν μΈμ μ΄ λκΈ°ν λκΈ° μ΄μ μ ν΄λΌμ΄μΈνΈκ° λ€λ₯Έ μλ²λ‘ μ μλ κ²½μ° μΈμ μ μμ κ²κ³Ό κ°μ μν©μ΄ λ°μνλ€λ λ¬Έμ κ° μλ€.
3 ) In-Memory Session Storage
μ΅κ·Ό κ°μ₯ λ§μ΄ μ¬μ©λλ κ²μ μΈμ λ§μ κ΄λ¦¬νκΈ° μν λ³λμ μλ²λ₯Ό μΆκ°νλ κ²μ΄λ€. κΈ°μ‘΄μ μΈμ μ DB μ μ μ₯νκΈ° λλ¬Έμ DB μ λΆλ΄ λν μ»ΈμΌλ©°, λλΆλΆμ μλ²κ° RDBMS λ₯Ό μ¬μ©νκΈ° λλ¬Έμ λ리기κΉμ§ νλ€.
Redis
μ Memcached
λ In-Memory μΊμ± μλ²λ₯Ό ꡬμΆν΄ μΈμ
μ μ μ₯νκ³ κ΄λ¦¬νλ€. λ°λΌμ λ§€μ° λΉ λ₯Έ μλλ‘ μΈμ
μ κ΄λ¦¬ν
μ μμ λΏ μλλΌ, λμ΄μ λ‘λλ°Έλ°μλ μΈμ
μ ꡬλΆν νμ μμ΄ λ‘λλ°Έλ°μ±μ΄λΌλ λ³Έλμ λͺ©μ μλ§ μ§μ€νλ©΄ λκ³ , μλ²μ DB μμ μΈμ
μλ²μκ² μ§μλ§ νλ©΄ λκΈ° λλ¬Έμ λΆλ΄μ΄ μ€μ΄λ λ€. λν μ Session Clustering κ³Ό κ°μ΄ λκΈ°ν λ¬Έμ λ μ‘΄μ¬νμ§ μλλ€.
μΈμ
μ μ¬μ©νλ MSA μμ€ν
μμ μΈμ
μ κ΄λ¦¬νλ κ°μ₯ 보νΈμ μΈ λ°©λ²μ΄λ€.
μ΄μ μ°λ¦¬λ μΈμ μ΄ μ μ§λμ§ μμ μ μλ λ¬Έμ μ λν΄μλ ν΄κ²°μ νλ€. νμ§λ§ μ μμκ° μ¦κ°νλ©΄ κ΄λ¦¬ν΄μΌν μΈμ μ΄ μ¦κ°νκ³ , μ΄μ° λμλ μΈμ μλ²μ λΆλ΄μ΄ 컀μ§λ κ²μ λ§μ μ μλ€. μΈμ μλ²μ ꡬμ μ΄λΌλ μΆκ° λΉμ© λλ AWS μ κ°μ ν΄λΌμ°λ νκ²½μ μ΄μ©ν κ²½μ° μΆκ° λΉμ©μ΄ μ²κ΅¬λλ κ²μ μ체 μλ²μ λ§λν μΈνλΌμ μΈλ ₯μ μ΄μ©νλ IT λκΈ°μ μ΄ μλ μ΄μ λΉμ© μΆκ°λ₯Ό κ°λΉν΄μΌνλ€.
3. Token π©βπ»
1. Public Key & Private Key
Session μ΄ μλ²μ λΆλ΄μ μ£Όλ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ μΈμ¦ μ 보λ₯Ό μλ²κ° μλ ν΄λΌμ΄μΈνΈμ μ μ₯νκ³ κ΄λ¦¬νλ ν ν° λ°©μμ΄ λ§μ΄ λμ λκ³ μλ€. μ΄ ν ν° λ°©μμ μ΄λ»κ² μλνκΈΈλ ν΄λΌμ΄μΈνΈμ μ μ₯νκ³ κ΄λ¦¬ν΄λ μμ ν κ²μΌκΉ? ν ν°μ μ΄ν΄νκΈ° μν΄μλ μ°μ Public Key μ Private Key λ₯Ό μ΄ν΄ν΄μΌνλ€.
μνΈνλ ν¬κ² νλμ ν€λ‘ μνΈν(Encryption)κ³Ό 볡νΈν(Decryption)μ λͺ¨λ ν μ μλ λμΉν€(Symmetric Key)μ λ κ°μ ν€κ° νλμ μμ΄ λμ΄ νλμ ν€λ‘ μνΈνλ₯Ό νλ©΄ λ°λμ ν€λ‘λ§ λ³΅κ΅¬ν μ μλ λΉλμΉν€(Asymmetric Key)κ° μλ€. μ΄ λΉλμΉν€μ μμ μ΄λ£¨λ ν€ μ€ νλλ₯Ό Public Key λΌ νκ³ , λ€λ₯Έ νλλ₯Ό Private Key λΌ νλ€.
λ¨μ λΉλμΉν€λ λ€μκ³Ό κ°λ€.
Aν€ | Bν€ |
---|---|
Aν€λ‘ λ°μ΄ν°λ₯Ό μνΈν | Aν€λ‘ μνΈν ν λ°μ΄ν°λ₯Ό Bν€λ‘ 볡νΈν |
Bν€λ‘ μνΈν ν λ°μ΄ν°λ₯Ό Aν€λ‘ 볡νΈν | Bν€λ‘ λ°μ΄ν°λ₯Ό μνΈν |
λ°λΌμ κΈ°λ³Έμ μΌλ‘ νλμ ν€ μμμ λ ν€λ μλ‘ λμΌν ꡬ쑰λ₯Ό κ°κ² λλ€. ν€ μμ λ§λ€μ΄ νλλ₯Ό λ°°ν¬νλ μκ° λ°°ν¬λμ§ μμ μͺ½μ΄ Private Key κ° λλ κ²μ΄κ³ , λ°°ν¬λ μͺ½μ΄ Public Key κ° λλ ꡬ쑰λ€.
νμ§λ§ ν°λ―Έλλ‘ ν€λ₯Ό μμ±ν΄λ³΄λ©΄ Private Key κ° Public Key μ©λμ΄ ν¨μ¬ ν¬κ² μμ±λλ€. μ κ·Έλ΄κΉ? μΌλ¨, Private Key κ° λ μ€μνκ³ , Private Key μλ§ νμν κΈ°λ₯μ΄ μΆκ°λκΈ° λλ¬Έμ΄λ€.
Private Key μ 첫 λ²μ§Έ μ°¨μ΄
Public Key λ 곡κ°λμ΄ μ μ‘λλ€. λμ μ μμ μ¦λͺ νκΈ°λ₯Ό μνλ κ³³μ μμμ΄ λ§μ΄ 볡μ λμ΄ μ μ‘λ κ²μ΄λ€. μκ³ λ¦¬μ¦μ΄ ν¬κ³ λ¬΄κ±°μΈ μλ‘ κ°λ ₯ν΄μ§μ§λ§ μ©λμ΄ μ¦κ°νκΈ° λλ¬Έμ μ λΉν μνΈν μ±λ₯μ μ λΉν ν¬κΈ°λ₯Ό κ°λ κ²μ΄ μ’λ€.
λ°λ©΄ Private Key λ κ°μΈ λλ κΈ°μ λ¨μΌ κ°μ±κ° κ°μ§κ³ μκΈ° λλ¬Έμ μ μ μ¦λͺ μΌλ‘ μ¬μ©ν μ μλ€. μ μμ μ¦λͺ νλ€λ κ²μ λ¨μν λ°μ΄ν°λ₯Ό μνΈν νλ κ²λ³΄λ€ λ μ€μν κΈ°λ₯μ΄λ€. λ°λΌμ μ΄λ μ μ‘ν νμλ μμ λΏ μλλΌ λ κ°λ ₯ν μκ³ λ¦¬μ¦μ μ μ©ν΄ μ μ νμΈ κΈ°λ₯μ κ°ννλ κ²μ΄ μ’κΈ° λλ¬Έμ λ ν¬κ³ λ¬΄κ±°μ΄ μκ³ λ¦¬μ¦μ μ¬μ©νλ€.
λ°λΌμ μΌλ°μ μΌλ‘ Public Key λ RSA 1024~4096 bit μ λμ μκ³ λ¦¬μ¦μ μ¬μ©νκ³ , Private Key λ RSA 2048~8192 bit μ μκ³ λ¦¬μ¦μ μ¬μ©νλ€. μ΄κ² Public Key μ Private Key μ 첫 λ²μ§Έ μ°¨μ΄λ€.
Private Key μ λ λ²μ§Έ μ°¨μ΄
μΈμ¦μ λ°κΈκΈ°κ΄ λ± μμ μΌμ΄λλ μ μ°¨λ μΌλ¨ 무μνκ³ μλ²μ ν΄λΌμ΄μΈνΈ κ΄μ μμ μ€λͺ ν΄λ³΄μ. μ΄μ μλ²λ μΈμ μ 보λ₯Ό DB μ μ μ₯νμ§ μκ³ μμ μ Private Key λ‘ μνΈν ν΄ ν΄λΌμ΄μΈνΈμκ² μλ²μμ κ΄λ¦¬νλ μΈμ¦ μ 보λ₯Ό λͺ¨λ κΈ°λ‘ν΄ λκ²¨μ£Όμ΄ ν΄λΌμ΄μΈνΈκ° κ΄λ¦¬νκΈ°λ‘ νλ€κ³ νμ. μ΄μ μΈκ°λ λ²μλΌλκ° μ ν¨ κΈ°κ°μ΄λΌλκ° νλ μΈμ μΌλ‘ κ΄λ¦¬νλ μ 보λ€μ΄ ν΄λΌμ΄μΈνΈμκ² μ μ‘λ κ²μ΄λ€.
- ν΄λΌμ΄μΈνΈκ° μλ²μκ² λ‘κ·ΈμΈ μμ²μ 보λΈλ€.
- μλ²λ ν΄λΌμ΄μΈνΈμκ² μμ μ Private Key λ‘ μνΈν ν λ°μ΄ν°λ₯Ό μΏ ν€μ μ€μ΄ 보λΈλ€.
- ν΄λΌμ΄μΈνΈλ μ΄λ―Έ μΈμ¦μ λ°κΈ κΈ°κ΄μ ν΅ν΄ μ¬λ°λ₯Έ μλ²μμ νμΈ νμ§λ§, μλ²μ Public Key λ₯Ό κ°κ³ μκΈ° λλ¬Έμ μ€κ°μ λ€νΈμν¬ λ³κ²½ λ±μΌλ‘ μλ²μ μ μ κ²½λ‘κ° λ³κ²½λλλΌλ ν΄λΉ μλ²μ λλ©μΈμ΄ λ³΄λΈ μλ΅μΈμ§ κ²μ¦μ΄ κ°λ₯νλ€.
- ν΄λΌμ΄μΈνΈλ μ΄μ μλ²μ λΉ λ₯΄κ² ν΅μ νκΈ° μν΄ μλ²μ Public Key λ‘ μμ μ PC κ° λ§λ λμΉν€λ₯Ό μνΈν ν΄ μΏ ν€μ ν¨κ» μ μ‘νλ€.
ν΄λΌμ΄μΈνΈ μ μ₯μμλ μλ²μ Private Key λ‘ λ°μ΄ν°λ₯Ό μ μΆνκΈ° λλ¬Έμ μλ²μ μ μμ΄ μ¦λͺ λλ ν¨κ³Όλ₯Ό κ°λλ€.
ν΄λΌμ΄μΈνΈλ μλ²μ Private Key λ₯Ό κ°μ§κ³ μμ§ μμΌλ ν ν°μ λ³μ‘°νλ κ²μ μ½μ§ μλ€. νμ§λ§ Public Key λ₯Ό κ°μ§κ³ μκΈ° λλ¬Έμ μμμ΄ λ³μ‘°νλ€ λ³΄λ©΄ Public Key λ‘ μ μ ν΄λ λλ λ³μ‘°λ ν ν° λ°μ΄ν°λ₯Ό μ»μ μ μλ€. μ΄λ κ² μ ν¨κΈ°κ°μ΄ λμ΄λκ±°λ μΈκ°λ λ²μκ° λμ΄λ λ³μ‘°λ ν ν°μ 보λμ λ μλ²λ ν ν°λ§ νμΈν΄μλ λ³μ‘° μ¬λΆλ₯Ό μ μ μλ€. μλ²λ μΈμ κ³Ό λ¬λ¦¬ ν ν°μ 보κ΄νκ³ μνκ΄λ¦¬λ₯Ό νμ§ μκΈ° λλ¬Έμ΄λ€. κ²°κ΅ μλ²λ μ΄κ²μ΄ λ³μ‘°λμ§ μμμμ κ²μ¦νκΈ° μν΄μ μΈμ μ²λΌ DB μ ν ν° μ 보λ₯Ό κΈ°λ‘νκ±°λ, ν ν°μ λ³μ‘°ν μ μλ 무μΈκ°λ₯Ό μΆκ°ν΄μΌνλ€.
κ·Έλμ μΆκ°λ κΈ°λ₯μ΄ μλͺ (Signature)μ΄λ€.
μλͺ
μ μν΄ Private Key μ SHA-256, SHA-512 μ κ°μ ν΄μ ν¨μμ secret-key
λ₯Ό μΆκ°νλ€. κ·Έλ¬λ©΄ Private Key λ ν ν°μ
μμ±ν λ ν΄μ± κ²°κ³Όλ₯Ό μλͺ
μΌλ‘ μΆκ°νλ€. ν΄μ ν¨μλ 볡νΈνκ° λΆκ°λ₯νκΈ° λλ¬Έμ ν΄λΌμ΄μΈνΈμμ λ³μ‘°νλ κ²μ΄ λΆκ°λ₯νλ€. μ΄μ μλ²λ
ν΄λΌμ΄μΈνΈκ° λ³΄λΈ ν ν°μ λ€μ ν΄μ±ν΄ μΆκ°λ μλͺ
μ μΌμΉ μ¬λΆλ₯Ό νμΈνλ©΄ ν ν°μ λ³μ‘° μ 무λ₯Ό νμΈν μ μκ² λλ κ²μ΄λ€.
μ¦, λ¨μν λΉλμΉν€λ‘λ§ μ¬μ©νλ€λ©΄ ν μμ΄ λλ λ κ°μ ν€λ₯Ό μμ± ν λ°°ν¬λλ μκ° Private/Public Key κ° λμ΄μΌνμ§λ§, μ΄λ° μ°¨μ΄μ μΌλ‘ μΈν΄ μ€μ λ‘λ μμ±νλ μκ° λΆν° Private/Public Key κ° μ ν΄μ§κ³ Private Key μ μ©λμ΄ λ ν° κ²μ΄λ€.
2. Token
μ΄μ ν ν°μ΄ μ€μ λ‘ μ΄λ»κ² μλνλμ§λ₯Ό νμΈν΄λ³΄μ.
- ν΄λΌμ΄μΈνΈκ° μλ²μ λ‘κ·ΈμΈ μμ².
- μλ²λ λ‘κ·ΈμΈμ μ²λ¦¬(authorization), μλ²μ Private Key λ‘ μμ±ν μΈκ°/λ§λ£μκ° λ± μμΈν μΈμ¦ μ λ³΄κ° λ΄κΈ΄ ν ν°μ ν΄λΌμ΄μΈνΈμ λ°ν(JWT, OAuth μ ννμ΄λ©° μ€μν κ²μ μ΄ κ³Όμ μμ authentication μ²λ¦¬κ° μλ authorization μ²λ¦¬λ₯Ό νλ€λ κ²μ΄λ€).
- ν΄λΌμ΄μΈνΈλ ν ν°κ³Ό ν¨κ» μλ²μ νμν μμ μ μμ².
- μλ²λ ν ν°μ μλͺ μ΄ μ ν¨νμ§ κ²μ¦. μλͺ μ΄ μ ν¨νλ€λ©΄ Public Key λ‘ ν ν°μ κΈ°λ‘λ μμΈν μΈμ¦ μ 보λ₯Ό νμΈν΄ ν ν°μ΄ μ ν¨νμ§ νμΈ ν μ¬μ©μ μμ²μ μ²λ¦¬ν΄ μλ΅.
μ΄μ μλ²λ λ§€λ² Session μ΄ μ μ₯λ DB μ 체λ₯Ό μ‘°νν΄ κ²μ¦νλ λμ μ¬μ©μκ° λ³΄κ΄νκ³ μλ ν ν°μ μ ν¨ μ¬λΆλ§ νμΈ ν μμ²μ μ²λ¦¬νκΈ° λλ¬Έμ μλ²μ λΆλ΄μ΄ μ€μ΄λ λ€. ν΄λΌμ΄μΈνΈλ μλ²μ λΉλ°ν€λ‘ μνΈνλ λ°μ΄ν°λ₯Ό ν΅ν΄ μλ²μ μ μμ νμΈνκ³ μ λ’°ν μ μμΌλ©°, μλ²λ μμ μ μλͺ μ μΆκ°ν΄ ν ν°μ΄ λ³μ‘°λμ§ μμμμ νμΈνκ³ μ λ’°ν μ μκΈ° λλ¬Έμ΄λ€.
νμ§λ§ ν ν°μλ μ·¨μ½μ μ μ‘΄μ¬νλ€. μλ²μμλ ν ν°μ κ΄ν μ΄λ€ μ 보λ 보κ΄νκ³ κ΄λ¦¬νμ§ μκΈ° λλ¬Έμ ν ν° μ체μ νμ·¨μ μ·¨μ½νλ€. κ·Έλ κΈ° λλ¬Έμ μλ²λ 짧μ μκ°μ κ°λ Access Token κ³Ό, κ°±μ μ μ¬μ©ν Refresh Token μ λ°ννλ€. νΉμλΌλ Access Token μ΄ μ μΆλλλΌλ Refresh Token μ΄ μλ€λ©΄ νμ·¨ν ν ν°μ μ§μμ μΌλ‘ μ¬μ©ν μ μλλ‘ νκΈ° μν¨μ΄λ€.
μΌλ°μ μΌλ‘ Access Token μ λΆ~μκ° λ¨μλ₯Ό κ°κ³ , Refresh Token μ μΌ~μ£Ό λ¨μλ₯Ό κ°λλ€. κ·Έλ¦¬κ³ μΌλ°μ μΌλ‘ λ‘κ·ΈμΈνλ νκ²½μ λ°λΌμλ ν ν°μ μ ν¨μκ°μ΄ λ€λ₯Έλ°, μ±μμ λ‘κ·ΈμΈ λλ κ²½μ° μ€λ§νΈν°μ κ°μΈ κΈ°κΈ°μ΄κΈ° λλ¬Έμ μ ν¨μκ°μ΄ λΉκ΅μ κΈ΄ ν ν°μ΄ λ°νλλ λ°λ©΄, μΉμμ λ‘κ·ΈμΈ λλ κ²½μ° κ³΅μ© κΈ°κΈ°μΌ μ μκΈ° λλ¬Έμ μ ν¨μκ°μ΄ λ§€μ° μ§§μ ν ν°μ΄ λ°νλλ€.
{
method: "GET",
headers: {
"Authorization": "Bearer ${JWT_TOKEN}"
}
}
μΌλ°μ μΌλ‘ μ΄λ° ννλ‘ ν΄λμ Bearer
prefix κ° ν¬ν¨λ ν ν°μ μ€μ΄ 보λΈλ€.
4. Session vs Token π©βπ»
Criteria | Session authentication method | Token-based authentication method |
---|---|---|
authentication μ 보 μ μ₯ | μλ²(μΌλ°μ μΌλ‘ ν¬κΈ°κ° μμ JSON ν¬λ§·) | ν΄λΌμ΄μΈνΈ(ν¬κΈ°κ° ν° ν ν°) |
authorization μ μν΄ ν΄λΌμ΄μΈνΈκ° μλ²μ μ μ‘νλ κ² | μΏ ν€(Session ID κ° ν¬ν¨λ) | ν ν° |
authorization κ²μ¦μ μν΄ μλ²μμ νλ κ² | μΈμ DB λ₯Ό μ‘°νν΄ μ ν¨μ± κ²μ¦ | ν ν°μ 볡νΈν νκ³ μλ©μ κ²μ¦ |
μλ² κ΄λ¦¬μκ° μ¬μ©μμ λ‘κ·Έμμ, authentication μ 보 λ³κ²½κ³Ό κ°μ 보μ μμ κ°λ₯ μ¬λΆ | κ°λ₯(μΈμ μ΄ μλ²μ μ μ₯) | λΆκ°λ₯(ν ν°μ΄ ν΄λΌμ΄μΈνΈ κΈ°κΈ°μ μ μ₯) |
μ·¨μ½μ | MITM, CSRF | MITM, ν ν° νμ·¨, breaches of the secret key |
μ νΈνλ κ³³ | User-to-server | Server-to-server |
μΌλ°μ μΌλ‘ μΈμ μ Application μμ μ¬μ©λκ³ ν ν°μ server-to-server ν΅μ μμ μ νΈλλ€. κ·Έ μ΄μ λ ν ν°μ μλ²κ° μλ ν΄λΌμ΄μΈνΈμμ κ΄λ¦¬νκ³ , μλ²λ λ³μ‘° μ¬λΆμ μ ν¨μ±λ§ κ²μ¦νκΈ° λλ¬Έμ Limitation of Session μ λͺ¨λ λ¬Έμ μ μμ ν΄λ°©λλ€. νΉν MSA κ°μ νκ²½μμ server-to-server ν΅μ μμ ν΄λΌμ΄μΈνΈμ μνλ₯Ό μ μ§ν νμ μμ΄ κ° μμ²μ λ 립μ μΌλ‘ μ²λ¦¬ν μ μμΌλ©°(stateless), μλ² κ°μ ν ν°μ μ μ‘νκ³ κ²μ¦λ§ νλ©΄ λλ―λ‘ λ€μμ μλ²κ° μ¬μ©μμ authentication/authorization μ μ½κ² 곡μ ν μ μμ΄ μλ²μ νμ₯μ±κ³Ό μ μ°μ±μ ν₯μ(scalability)μν€κΈ° λλ¬Έμ΄λ€.
μ¬μ ν μΈμ λ§ μ¬μ©νλ κ³³λ μμΌλ©°, ν ν°λ§ μ¬μ©νλ κ³³λ μκ³ μμ΄μ μ¬μ©νλ κ³³λ μλ€. νΉν κΈμ΅κΆ κ°μ΄ λ€μ€ μ μμ μ¬ννλ κ²½μ°λ μΈμ μ΄ λ°λμ νμνλ€. λ°λ©΄ μ μ κΈ°κΈ°λ₯Ό μ νν νμκ° ν¬μ§ μμ κ²½μ° λ©ν° λλ°μ΄μ€μ λ°λ₯Έ μΈμ κ΄λ¦¬ λΆλ΄μ΄ λμ± μ»€μ§κΈ° λλ¬Έμ μΈμ λμ ν ν°μ μ¬μ©νλ©΄ μλ²μ λΆλ΄μ ν¬κ² μ€μΌ μ μλ€. λ¬Όλ‘ , μ μ κΈ°κΈ°λ₯Ό μ νν΄μΌν΄μ μΈμ μ μ¬μ©νλ κ²½μ°λ server-to-server ν΅μ μ ν ν°μ μ¬μ©νλ©΄ ν΅μ λΉμ©μ ν¬κ² μ€μΌ μ μμ΄ μΈμ λ§ μ¬μ©νλ κ²λ³΄λ€ ν¨μ¨μ μ΄λ€.
λ€νΈμν¬ νκ²½μ΄ μ’μ§ λͺ»νλ©° λͺ¨λ°μΌ κΈ°κΈ°μΈ κ²½μ° ν ν°μ΄ μΈμ λ³΄λ€ μ©λμ΄ μ»€ μ±λ₯μ μν₯μ λ―ΈμΉ μ μλ λ§€μ° νΉμν μν©λ§ μλλΌλ©΄ ν ν°μ΄ κ°λ μ₯μ μ΄ ν¬κΈ° λλ¬Έμ λ§μ μλ²κ° ν ν°μ λμ νλ κ²μ΄λ€.
5. Web Storage π©βπ»
1. Web Storage API
λΈλΌμ°μ κ° λ°μ΄ν°λ₯Ό μ μ₯νλ λ°©λ²μ ν¬κ² Web Storage API μ IndexedDB API λλ‘ λλλ€. Web Storage API λ λΈλΌμ°μ μ μ μ₯λλ©° λΈλΌμ°μ μ μν΄ κ΄λ¦¬λλ©° λ€μκ³Ό κ°μ νΉμ§μ κ°λλ€.
- λλ©μΈ λ¨μλ‘ μ μ₯(μΏ ν€μ λμΌ)
- λΈλΌμ°μ λ§λ€ λ€λ₯΄μ§λ§ 5~10MB λ₯Ό μ μ₯νλ€(μΏ ν€μ λ¬λ¦¬ μ¬μ΄νΈλ³ κ°λ³ μ©λμ΄ μλ λΈλΌμ°μ μ 체μ μ©λμ΄λ€)
- μꡬ μ μ₯ κ°λ₯(session λ¨μ λλ μꡬ μ μ₯)
μΏ ν€μ λΉκ΅νλ©΄ μμ²λκ² ν° μ©λμ μ μ₯ν μ μμ§λ§ μ¬μ΄νΈλ³λ‘ μ£Όμ΄μ§λ μ©λμ μλλ°λ€ λλν μ©λλ μλλ€. νμ§λ§ μΏ ν€μ λΉν΄μλ
κ½€ λλν μ©λμ μ¬μ©ν μ μμ λΏ μλλΌ μΏ ν€λ λͺ¨λ λ€νΈμν¬ μμ²μ ν¬ν¨λκΈ° λλ¬Έμ ν΅μ μ λΆνμν λ°μ΄ν°λ Local Storage
λλ
Session Storage
μ μ μ₯νλ κ²μ΄ κΆμ₯λλ€.
Local Storage μ Session Storage μ μ¬μ©λ²μ λμΌνλ€. λ¨μ§ Session Storage λ μΈμ μ΄ λ§λ£λλ©΄ μλμΌλ‘ μμ λλ λ°λ©΄, Local Storage λ λ³λλ‘ μ κ±°νμ§ μλ ν μꡬ μ μ₯μ΄ κ°λ₯νλ€.
μ¬μ© κ°λ₯ν μ£Όμ λ©μλλ λ€μκ³Ό κ°λ€.
getItem(key:)
: λ°μ΄ν° μ‘°νsetItem(key:value:)
: λ°μ΄ν° μΆκ°removeItem(key:)
: λ°μ΄ν° μ κ±°clear()
: μ€ν λ¦¬μ§ μ΄κΈ°ν
κ·Έλ¦¬κ³ Web Storage μ λ°μ΄ν°λ₯Ό μ μ₯ν λ λͺ¨λ λ°μ΄ν°λ JSON λ¬Έμμ΄λ‘ λ³νν΄ μ μ₯νλλ‘ ν΄μΌνλ€. κ·Έλ μ§ μμΌλ©΄ μ μ₯λ λ°μ΄ν°λ₯Ό κ°μ Έμ¬ λ, μλ³Έ λ°μ΄ν°κ° κ°μ²΄μλμ§λ ꡬλΆν΄ λ€μ JavaScript κ°μ±λ‘ νμ±νκ±°λ μλ¬κ° λ°μν λλ§λ€ μ²λ¦¬νλλ‘ μμΈμ²λ¦¬λ₯Ό ν΄μΌνκΈ° λλ¬Έμ΄λ€.
const setStorage = (storage) => (key, value) => storage.setItem(key, JSON.stringify(value));
export const setLocalStorage = setStorage(localStorage);
export const setSessionStorage = setStorage(sessionStorage);
const getStorage = (storage) => (key) => storage.getItem(key);
export const getLocalStorage = getStorage(localStorage);
export const getSessionStorage = getStorage(sessionStorage);
const removeStorage = (storage) => (key) => storage.removeItem(key);
export const removeLocalStorage = removeStorage(localStorage);
export const removeSessionStorage = removeStorage(sessionStorage);
const clearStorage = (storage) => () => storage.clear();
export const clearLocalStorage = clearStorage(localStorage);
export const clearSessionStorage = clearStorage(sessionStorage);
2. IndexedDB API
IndexedDB API λ MSW(Mock Service Worker) μ²λΌ Web Worker λ₯Ό μ¬μ©ν΄ λ°μ΄ν°λ₯Ό μ μ₯νλ API λ‘ λΉλκΈ° μ²λ¦¬λμ΄ μ±λ₯μ μν₯μ λ―ΈμΉμ§ μλ Low Level Local Storage λ€. μ΄κ²μ μμ΄ν°μΌλ‘ μΉλ©΄ CoreData, SwiftData, Realm μ΄λ° κ²κ³Ό κ°μλ° RDBMS λ μλλ°λ€ NoSQL λ μλμ§λ§ κ·Έλλ NoSQL μͺ½μ μ‘°κΈ λ κ°κΉλ€. Firebase Storage λλ Realm, MongoDB λ₯Ό λ€λ£¨λ― μ€ν 리μ§λ₯Ό λ€λ£¬λ€.
IndexedDB API λ λ€μκ³Ό κ°μ νΉμ§μ κ°λλ€.
- λλ©μΈ λ¨μλ‘ μ μ₯
- λΈλΌμ°μ λ§λ€ λ€λ₯΄μ§λ§ 1TB λμ€ν¬ κΈ°μ€μΌλ‘ 600GB κΉμ§ νμ₯ κ°λ₯
- μꡬ μ μ₯ κ°λ₯(persistent μ€μ κ°λ₯)
κΈ°λ³Έμ μΌλ‘ μΈμ
λ¨μλ‘ μ μ₯λμ§λ μμ μΈμ
μ΄ λ겨λ λ°μ΄ν°λ μ ν¨νλ€. λμ€ν¬κ° λΆμ‘±ν κ²½μ° λΈλΌμ°μ λ μ΄μ체μ μ μν΄ μ 리λ μ μμΌλ
persistent
λ₯Ό μ§μ νλ©΄ λΈλΌμ°μ λ§λ€ λ€λ₯΄μ§λ§ 1GB κ°λ μꡬ μ μ₯μ΄ κ°λ₯νλ€.
3. IndexedDB Examples
<form id="customerInputForm">
<label for="userId">μμ΄λ:</label>
<input type="text" id="userId" name="userId" />
<label for="name">μ΄λ¦:</label>
<input type="text" id="name" name="name" />
<button type="submit" class="btn--green">μ μ₯</button>
<button type="reset">μλ‘κ³ μΉ¨</button>
</form>
<ul id="customerList"></ul>
input {
outline: none;
border: none;
padding: 1px 0;
}
#customerInputForm,
#customerList {
margin: 10px 0;
padding: 0;
color: black;
font-weight: 700;
}
#customerInputForm,
#customerList li {
height: 40px;
padding: 10px;
margin: 10px 0;
background-color: #8aadc1;
box-sizing: border-box;
border: none;
border-radius: 15px;
display: flex;
align-items: center;
}
#customerList li span {
width: 70px;
}
#customerInputForm input,
#customerList input {
width: 150px;
margin: 0 10px 0 5px;
user-select: none;
}
#customerList li input {
background-color: #bcbcbc;
}
#customerList li.edit input {
background-color: #fff;
}
#customerInputForm button,
#customerList button {
margin-right: 5px;
padding: 3px 15px;
border: none;
border-radius: 4px;
font-weight: 700;
}
#customerList.edit button {
display: none;
}
#customerList li.edit button {
display: block;
}
#customerList li button.hidden {
display: none;
}
button.btn--red {
background-color: #ff3b3b;
}
button.btn--green {
background-color: #20e62a;
}
const customerForm = document.querySelector('#customerInputForm');
const customerList = document.querySelector('#customerList');
const DATABASE_NAME = 'customer-database';
const TABLE_NAME = 'customer';
let db;
const actions = createActions();
const DBOpenRequest = indexedDB.open(DATABASE_NAME);
DBOpenRequest.onerror = (event) => {
customerList.innerHTML = `<li>Error loading database.</li>`;
console.error(event.target.error);
};
// λ²μ μ
κ·Έλ μ΄λκ° νμνκ±°λ μ΅μ΄ μμ±μ μνλλ μ½λλ‘ ν
μ΄λΈ, 컬λΌκ³Ό κ°μ λ°μ΄ν°λ² μ΄μ€ ꡬ쑰λ₯Ό μ μνλ€.
DBOpenRequest.onupgradeneeded = (event) => {
db = event.target.result;
// μ€ν μ΄ μμ±(ν
μ΄λΈ μμ±μ ν΄λΉ)
// index λ₯Ό μμ±ν΄μ κ²μμΌλ‘ μ¬μ©ν μλ μλ€. μ¦, autoIncrement λ₯Ό μ¬μ©ν΄ κ²μμ ν μ μκ³ ,
// μ€ν μ΄λ₯Ό κ΄λ¦¬νκΈ° μν primaryKey λλ keyλ‘ openCursor μμ κ²μ κ²°κ³Όμ λμ€λ
// primaryKey λλ key μ κ°μ΄ value μ λ±λ‘λ κ°κ³Ό λμΌνλ€.
const objectStore = db.createObjectStore(TABLE_NAME, {
keyPath: 'id',
autoIncrement: true,
});
// μΈλ±μ€ μμ±(μ»¬λΌ μμ±μ ν΄λΉ)
objectStore.createIndex('userId', 'userId', { unique: true });
objectStore.createIndex('name', 'name', { unique: false });
customerList.innerHTML += '<li>Object store created.</li>';
};
DBOpenRequest.onsuccess = (event) => {
customerList.innerHTML += '<li>Database initialised.</li>';
db = DBOpenRequest.result; // equal to 'event.target.result'
actions.load();
};
function createActions() {
let [prevUserId, prevUserName] = [null, null];
function idValidation(userId) {
if (userId.trim().length < 5) {
alert('μμ΄λλ 5μ μ΄μ μ
λ ₯ν΄μ£ΌμΈμ');
return false;
}
return true;
}
function addCustomer() {
const customer = {
userId: customerForm[0].value,
name: customerForm[1].value,
};
if (!idValidation(customer.userId)) return;
// νΈλμμ
μ μ΄ μ€ν μ΄ μ΄λ¦ λ°°μ΄, νΈλμμ
λͺ¨λλ₯Ό μ§μ
const transaction = db.transaction([TABLE_NAME], 'readwrite');
// νΈλμμ
μ μ΄λ¦° μ€ν μ΄ λ°°μ΄ μ€ λ¨μΌ μ€ν μ΄ μ κ·Ό
const objectStore = transaction.objectStore(TABLE_NAME);
// μμ² μ²λ¦¬
objectStoreRequest = objectStore.add(customer);
objectStoreRequest.onsuccess = (event) => {
customerForm.reset();
};
objectStoreRequest.onerror = (event) => {
debugger;
let message =
event.target.error.code === 0
? 'μμ΄λλ μ€λ³΅λ μ μμ΅λλ€'
: 'μ μ₯μ μ€ν¨νμ΅λλ€';
alert(message);
console.error({
Code: event.target.error.code,
Name: event.target.error.name,
Message: event.target.error.message,
});
};
}
function loadCustomers() {
customerList.innerHTML = '';
const objectStore = db.transaction(TABLE_NAME).objectStore(TABLE_NAME);
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor === null) return;
// μ€ν μ΄μ 'Key'κ° νλμ΄λ―λ‘ cursor.primaryKey, cursor.key, value.id 3κ°λ λͺ¨λ λμΌν κ°μ΄λ€.
const { id, userId, name } = cursor.value;
customerList.innerHTML += `
<li>
<span data-id="${id}">ID: ${id}</span>
μμ΄λ: <input type="text" value="${userId}" readonly />
μ΄λ¦: <input type="text" value="${name}" readonly />
<button type="button" data-type="editMode">μμ </button>
<button type="button" data-type="delete" class="btn--red">μμ </button>
<button type="button" data-type="cancel" class="hidden">μ·¨μ</button>
<button type="button" data-type="edit" class="btn--green hidden">μ μ₯</button>
</li>
`;
// Move on to the next cursor item
cursor.continue();
};
request.onerror = (event) => {
customerList.innerHTML = '<li>Store access error</li>';
console.error(event.target.error);
};
}
function toggleEditMode({ parentEl }) {
const [, userId, name, editModeBtn, deleteBtn, cancelBtn, editBtn] = [
...parentEl.children,
];
[customerList, parentEl].forEach((el) => el.classList.toggle('edit'));
userId.readOnly = !userId.readOnly;
name.readOnly = !name.readOnly;
[editModeBtn, deleteBtn, cancelBtn, editBtn].forEach((el) =>
el.classList.toggle('hidden')
);
}
function openEditMode({ parentEl }) {
const [, userId, name] = [...parentEl.children];
prevUserId = userId.value;
prevUserName = name.value ?? null;
toggleEditMode({ parentEl });
}
function cancelEditMode({ parentEl }) {
const [, userId, name] = [...parentEl.children];
userId.value = prevUserId && prevUserId;
name.value = prevUserName && prevUserName;
[prevUserId, prevUserName] = [null, null];
toggleEditMode({ parentEl });
}
function editCustomer({ id, userId, name }) {
if (!idValidation(userId)) return;
const objectStore = db
.transaction(TABLE_NAME, 'readwrite')
.objectStore(TABLE_NAME);
const updateRequest = objectStore.put({ id, userId, name });
updateRequest.onsuccess = (event) => {
loadCustomers();
customerList.classList.toggle('edit');
};
}
function deleteCustomer({ id }) {
const objectStore = db
.transaction(TABLE_NAME, 'readwrite')
.objectStore(TABLE_NAME);
const deleteRequest = objectStore.delete(id); // primaryKey λ‘ μμ
deleteRequest.onsuccess = (event) => loadCustomers();
// μμ΄ν
νΉμ κ°μΌλ‘ μμ νκ³ μΆμ κ²½μ° μ€ν μ΄λ‘λΆν° index λ₯Ό μμ±νκ³ κ°μ κ²μν΄
// κ²μλ μμ΄ν
μΌλ‘λΆν° primaryKey λ₯Ό κ°μ Έμ μμ .
/*
const nameRequest = objectStore.index('name').openCursor(name);
nameRequest.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor ===null) return;
objectStore.delete(cursor.primaryKey)
// cursor.continue(); // 볡μ μμ μΌ κ²½μ°
};
*/
}
return {
add: addCustomer,
load: loadCustomers,
editMode: ({ parentEl }) => openEditMode({ parentEl }),
edit: ({ id, userId, name }) => editCustomer({ id, userId, name }),
cancel: ({ parentEl }) => cancelEditMode({ parentEl }),
delete: ({ id }) => deleteCustomer({ id }),
};
}
customerForm.addEventListener('submit', (event) => {
event.preventDefault();
actions.add();
});
customerForm.addEventListener('reset', actions.load);
customerList.addEventListener('click', (event) => {
event.stopPropagation();
if (event.target.tagName !== 'BUTTON') return;
const li = event.target.parentElement;
const [id, userId, name, parentEl] = [
parseInt(li.firstElementChild.dataset.id),
li.children[1].value,
li.children[2].value,
li,
];
const type = event.target.dataset.type;
actions[type]({ id, userId, name, parentEl });
});
transaction
λ©μλλ string μΌλ‘ λ¨μΌ ν
μ΄λΈ λλ λ°°μ΄λ‘ μ¬λ¬ ν
μ΄λΈμ ν λ²μ νΈλμμ
μΌλ‘ μ΄ μ μμΌλ,
objectStore
λ©μλλ μ΄λ¦° νΈλμμ
μ€μμ λ¨μΌ ν
μ΄λΈμ μ΄μ΄μ μμ
νλ€.
μ€ν μ΄λ₯Ό μ° ν CRUD μ μ¬μ©νλ λ©μλλ λ€μκ³Ό κ°λ€.
- C:
add
λ©μλ - R:
cursor
λλindex
λ©μλλ‘ μΈλ±μ€λ₯Ό μμ± νget
μ΄λgetAll
λ©μλ μ κ·Ό - U:
put
λ©μλ - D:
delete
λ©μλ
store
λ₯Ό μμ±ν λ λ°λμ key
λ₯Ό νλ μ§μ ν΄μΌνκ³ μ΄κ²μ΄ νλμ΄λ©΄ κΈ°λ³Έμ μΌλ‘ primaryKey
λ‘ μ¬μ©λλ€. νμ§λ§
μ΄ Key λ λ°μ΄ν°μ Update, Delete μ κ°μ μμ
μ ν λ νμν κ°μΌλ‘, autoIncrement λ‘ μ€μ ν κ²½μ° μ§μ
컬λΌμ ν΄λΉνλ index
λ₯Ό μμ±ν κ²μ΄ μλκΈ° λλ¬Έμ index
λ©μλλ₯Ό μ¬μ©ν μ μμμ μ£Όμνλ€.
Reference
- λ°μμ , βνλ‘ νΈμλ μΉ κ°λ°μ λͺ¨λ κ² μ΄κ²©μ°¨ ν¨ν€μ§ Online.β fastcampus.co.kr. last modified unknown, Fast Campus.
- βUsing HTTP cookies.β MDN Web Docs. Feb. 23, 2024, accessed Apr. 18, 2024, MDN - HTTP cookies.
- βSession vs Token Based Authentication.β GeeksforGeeks. Jul. 4, 2022, accessed Apr. 18, 2024, Session vs Token Based Authentication.