클라이언트(브라우저)에 저장되는 작은 데이터 파일
- Key-Value 형태
- 서버가 생성하여 클라이언트에 전달
- 매 요청마다 자동으로 서버에 전송
1. 로그인 요청
Client Server
| |
| POST /login |
| username=admin |
|----------------------->|
| |
| Set-Cookie: |
| sessionId=abc123 |
|<-----------------------|
| |
(쿠키 저장: sessionId=abc123)
2. 이후 요청 (자동으로 쿠키 전송)
| |
| GET /profile |
| Cookie: sessionId=abc123|
|----------------------->|
| |
1. Expires / Max-Age:
- 만료 시간
- 설정 안 하면 세션 쿠키 (브라우저 종료 시 삭제)
2. Domain:
- 쿠키가 전송될 도메인
- 예: domain=example.com
3. Path:
- 쿠키가 전송될 경로
- 예: path=/admin
4. Secure:
- HTTPS에서만 전송
5. HttpOnly:
- JavaScript로 접근 불가
- XSS 공격 방지
6. SameSite:
- Strict: 같은 사이트에서만
- Lax: GET 요청은 허용
- None: 모든 사이트 (Secure 필수)
1. 세션 쿠키 (Session Cookie):
- 브라우저 종료 시 삭제
- 만료 시간 없음
2. 영구 쿠키 (Persistent Cookie):
- 만료 시간까지 유지
- 브라우저 종료해도 남음
3. 보안 쿠키 (Secure Cookie):
- HTTPS에서만 전송
4. HttpOnly 쿠키:
- JavaScript 접근 불가
장점:
✔ 자동 전송
✔ 구현 간단
✔ 서버 부하 없음
단점:
✘ 보안 취약 (클라이언트 저장)
✘ 용량 제한 (4KB)
✘ XSS 공격 위험
✘ 모바일 앱에서 사용 어려움
서버에 저장되는 사용자 정보
- 클라이언트는 Session ID만 보관
- 실제 데이터는 서버에 안전하게 저장
- 쿠키를 이용해 Session ID 전달
1. 로그인
Client Server
| |
| POST /login |
| username=admin |
| password=1234 |
|----------------------->|
| |
| (세션 생성)
| Session Store:
| sessionId: abc123
| user: {id:1, name:"admin"}
| |
| Set-Cookie: |
| sessionId=abc123 |
|<-----------------------|
| |
(쿠키 저장: sessionId=abc123)
2. 인증된 요청
| |
| GET /profile |
| Cookie: sessionId=abc123|
|----------------------->|
| |
| (세션 조회)
| Session Store에서
| abc123 찾기
| → user 정보 반환
| |
| User Profile Data |
|<-----------------------|
| |
3. 로그아웃
| |
| POST /logout |
| Cookie: sessionId=abc123|
|----------------------->|
| |
| (세션 삭제)
| Session Store에서
| abc123 제거
| |
| Set-Cookie: |
| sessionId=; expires=0 |
|<-----------------------|
| |
1. 메모리 (개발 환경):
- 빠름
- 서버 재시작 시 삭제
- 단일 서버만 가능
2. Redis (추천):
- 빠름 (인메모리 DB)
- 영구 저장 가능
- 여러 서버 공유 가능
3. 데이터베이스:
- 영구 저장
- 느림
- 안정적
4. 파일 시스템:
- 간단
- 느림
- 관리 어려움
// Node.js Express + Redis
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const redisClient = redis.createClient();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'my-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24, // 1일
httpOnly: true,
secure: true
}
}));
// 로그인
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 인증 확인
if (authenticate(username, password)) {
// 세션에 사용자 정보 저장
req.session.user = {
id: 1,
username: username
};
res.send('Login success');
}
});
// 인증 확인
app.get('/profile', (req, res) => {
if (req.session.user) {
res.json(req.session.user);
} else {
res.status(401).send('Unauthorized');
}
});
// 로그아웃
app.post('/logout', (req, res) => {
req.session.destroy();
res.send('Logout success');
});
장점:
✔ 보안성 높음 (서버 저장)
✔ 용량 제한 없음
✔ 서버에서 제어 가능
단점:
✘ 서버 메모리/저장소 사용
✘ 서버 부하 증가
✘ 확장성 낮음 (여러 서버 시)
✘ Redis 등 추가 인프라 필요
JSON 형태의 자체 포함 토큰
- 서버에 저장하지 않음
- 토큰 자체에 정보 포함
- 서명으로 무결성 검증
Header.Payload.Signature
예:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1. Header (헤더):
{
"alg": "HS256", // 알고리즘
"typ": "JWT" // 타입
}
2. Payload (페이로드):
{
"sub": "1234567890", // 사용자 ID
"name": "John Doe", // 사용자 이름
"iat": 1516239022, // 발급 시간
"exp": 1516242622 // 만료 시간
}
3. Signature (서명):
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
1. 로그인 (토큰 발급)
Client Server
| |
| POST /login |
| username=admin |
| password=1234 |
|----------------------->|
| |
| (JWT 생성)
| Header + Payload + Signature
| |
| Response: |
| { |
| token: "eyJhbG..." |
| } |
|<-----------------------|
| |
(토큰 저장: localStorage/sessionStorage)
2. 인증된 요청
| |
| GET /profile |
| Authorization: |
| Bearer eyJhbG... |
|----------------------->|
| |
| (토큰 검증)
| 1. 서명 확인
| 2. 만료 확인
| 3. Payload 추출
| |
| User Profile Data |
|<-----------------------|
| |
3. 로그아웃
| |
| POST /logout |
|----------------------->|
| |
(토큰 삭제: localStorage에서 제거)
// Node.js
const jwt = require('jsonwebtoken');
const SECRET_KEY = 'my-secret-key';
// 토큰 생성
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (authenticate(username, password)) {
const payload = {
id: 1,
username: username
};
const token = jwt.sign(
payload,
SECRET_KEY,
{ expiresIn: '1h' } // 1시간 후 만료
);
res.json({ token });
}
});
// 토큰 검증 미들웨어
function verifyToken(req, res, next) {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) {
return res.status(401).send('No token');
}
try {
const decoded = jwt.verify(token, SECRET_KEY);
req.user = decoded;
next();
} catch (err) {
res.status(401).send('Invalid token');
}
}
// 인증 필요한 라우트
app.get('/profile', verifyToken, (req, res) => {
res.json(req.user);
});
등록된 클레임 (Registered Claims):
- iss (issuer): 발급자
- sub (subject): 주제 (사용자 ID)
- aud (audience): 대상
- exp (expiration): 만료 시간
- nbf (not before): 활성 시작 시간
- iat (issued at): 발급 시간
- jti (JWT ID): 고유 식별자
공개 클레임 (Public Claims):
- 사용자 정의 (충돌 방지 필요)
비공개 클레임 (Private Claims):
- 당사자 간 합의한 정보
- 예: name, email, role
문제:
- Access Token 만료 시간이 짧음 (15분)
- 자주 재로그인 불편
해결:
Access Token + Refresh Token 방식
Access Token:
- 짧은 만료 시간 (15분)
- API 요청에 사용
Refresh Token:
- 긴 만료 시간 (7일)
- Access Token 재발급에 사용
- HttpOnly 쿠키에 저장
동작:
1. 로그인 → Access + Refresh Token 발급
2. API 요청 → Access Token 사용
3. Access Token 만료 → Refresh Token으로 재발급
4. Refresh Token도 만료 → 재로그인
장점:
✔ Stateless (서버 저장 불필요)
✔ 확장성 좋음 (여러 서버)
✔ 모바일 앱 친화적
✔ 서버 부하 적음
✔ CORS 문제 없음
단점:
✘ 토큰 크기 큼 (쿠키보다)
✘ 탈취 시 만료까지 유효
✘ Payload 노출 (암호화 안 됨)
✘ 강제 로그아웃 어려움
쿠키:
- 클라이언트 저장
- JavaScript로 접근 가능 (HttpOnly 없으면)
- XSS 공격 위험
- 보안: 낮음 ✘
세션:
- 서버 저장
- Session ID만 클라이언트
- 중요 정보는 서버에
- 보안: 높음 ✔
토큰:
- 클라이언트 저장
- Payload 노출 (Base64)
- 서명으로 무결성 보장
- 보안: 중간
쿠키:
- 서버 상태 없음
- 확장성: 좋음 ✔
세션:
- 서버마다 세션 다름
- Sticky Session 또는 중앙 저장소 필요
- 확장성: 나쁨 ✘
[Server1] ─┐
[Server2] ─┼─ Redis (세션 저장소)
[Server3] ─┘
토큰:
- 서버 상태 없음
- 어느 서버든 검증 가능
- 확장성: 좋음 ✔
쿠키:
- 자동 전송
- 서버 조회 없음
- 성능: 좋음 ✔
세션:
- 매 요청마다 저장소 조회
- Redis 사용 시 빠름
- 성능: 중간
토큰:
- 서버 조회 없음
- 서명 검증만
- 성능: 좋음 ✔
쿠키:
- 만료 시간 설정
- 클라이언트에서 삭제
- 제어: 약함
세션:
- 서버에서 삭제 가능
- 강제 로그아웃 가능
- 제어: 강함 ✔
토큰:
- 만료 시간 설정
- 강제 만료 어려움
- Blacklist 필요
- 제어: 약함
쿠키:
- 클라이언트 저장 (4KB)
- 자동 전송
- 보안 낮음
세션:
- 서버 저장 (무제한)
- Session ID만 클라이언트
- 보안 높음, 확장성 낮음
토큰 (JWT):
- 클라이언트 저장
- 자체 포함 (Payload)
- Stateless, 확장성 높음
선택:
- 단순 데이터 → 쿠키
- 전통적 웹 → 세션
- REST API, 모바일 → JWT
쿠키 (Cookie):
특징:
- 클라이언트(브라우저)에 저장
- Key-Value 형태
- 자동으로 서버에 전송
장점:
- 구현 간단
- 서버 부하 없음
단점:
- 보안 취약 (XSS 공격)
- 용량 제한 (4KB)
- 모바일 앱에서 어려움
사용:
- 언어 설정, 테마 등
- 간단한 사용자 기본 설정
세션 (Session):
특징:
- 서버에 저장
- Session ID만 클라이언트 (쿠키)
- 실제 데이터는 서버
동작:
1. 로그인 → 세션 생성
2. Session ID를 쿠키로 전달
3. 매 요청마다 세션 조회
장점:
- 보안성 높음
- 용량 제한 없음
- 강제 로그아웃 가능
단점:
- 서버 메모리/저장소 사용
- 확장성 낮음 (여러 서버 시)
- Redis 등 추가 인프라
사용:
- 전통적인 웹 애플리케이션
- 보안이 중요한 경우
토큰 (JWT):
특징:
- JSON 형태의 토큰
- Header.Payload.Signature
- 자체 포함 (Self-Contained)
동작:
1. 로그인 → JWT 발급
2. 클라이언트 저장 (localStorage)
3. Authorization 헤더로 전송
4. 서버가 서명 검증
장점:
- Stateless (서버 저장 불필요)
- 확장성 높음 (Scale-Out)
- 모바일 앱 친화적
- CORS 문제 없음
단점:
- 토큰 크기 큼
- 탈취 시 위험 (만료까지 유효)
- Payload 노출 (암호화 안 됨)
- 강제 로그아웃 어려움
보안 강화:
- Refresh Token 사용
- 짧은 만료 시간
- HTTPS 사용
- XSS/CSRF 방지
선택 기준:
- 단순 설정 저장 → 쿠키
- 전통적 웹 (단일 서버) → 세션
- REST API, MSA → JWT
- 모바일 앱 → JWT
- 보안 최우선 → 세션
- 확장성 최우선 → JWT
도메인 이름을 IP 주소로 변환하는 시스템
- 인터넷의 전화번호부
- 사람이 읽기 쉬운 이름 → 컴퓨터가 이해하는 IP
www.example.com → 93.184.216.34
사람: www.example.com (기억하기 쉬움)
컴퓨터: 93.184.216.34 (실제 주소)
1. 브라우저에 www.example.com 입력
2. DNS 조회 시작
↓
3. 로컬 캐시 확인
↓ (없으면)
4. Recursive DNS 서버 (ISP)
↓
5. Root DNS 서버
↓
6. TLD DNS 서버 (.com)
↓
7. Authoritative DNS 서버 (example.com)
↓
8. IP 주소 반환 (93.184.216.34)
↓
9. 브라우저가 해당 IP로 접속
Client DNS Servers
1. www.example.com 입력
|
2. 로컬 캐시 확인
브라우저 캐시
OS 캐시
hosts 파일
|
(없으면)
|
3. Recursive DNS 서버
(ISP DNS: 168.126.63.1)
|
| "www.example.com?"
|------------------------>|
| |
| 4. Root DNS 서버 질의
| | "com은 어디?"
| |-------------> [Root]
| | "TLD로 가"
| |<-------------
| |
| 5. TLD DNS 서버 질의
| | "example.com은 어디?"
| |-------------> [.com TLD]
| | "Authoritative로 가"
| |<-------------
| |
| 6. Authoritative DNS 질의
| | "www.example.com은?"
| |-------------> [example.com NS]
| | "93.184.216.34"
| |<-------------
| |
| "93.184.216.34" |
|<------------------------|
|
7. IP 주소로 접속
|
| HTTP GET /
|-----------------------> 93.184.216.34
역할:
- 최상위 DNS 서버
- TLD 서버 위치 안내
특징:
- 전 세계 13개 (A~M)
- Anycast로 복제본 수백 개
- 예: a.root-servers.net
응답:
"com 도메인은 TLD 서버로 가세요"
역할:
- 최상위 도메인 관리
- .com, .net, .org, .kr 등
예:
.com TLD: com 도메인 관리
.kr TLD: kr 도메인 관리
응답:
"example.com은 Authoritative 서버로 가세요"
역할:
- 실제 도메인 정보 보유
- 최종 IP 주소 제공
예:
example.com의 네임서버
- ns1.example.com
- ns2.example.com
응답:
"www.example.com = 93.184.216.34"
역할:
- 사용자 대신 DNS 조회
- 캐싱
제공자:
- ISP (KT, SK, LG)
- Google (8.8.8.8)
- Cloudflare (1.1.1.1)
특징:
- 전체 조회 과정 수행
- 결과 캐싱으로 빠른 응답
도메인 → IPv4 주소
예:
www.example.com. IN A 93.184.216.34
도메인 → IPv6 주소
예:
www.example.com. IN AAAA 2606:2800:220:1:248:1893:25c8:1946
도메인 → 다른 도메인 (별칭)
예:
blog.example.com. IN CNAME example.com.
blog.example.com 접속 → example.com으로 리다이렉트
이메일 서버 지정
예:
example.com. IN MX 10 mail.example.com.
우선순위↑ 메일서버↑
낮을수록 우선순위 높음
네임서버 지정
예:
example.com. IN NS ns1.example.com.
example.com. IN NS ns2.example.com.
텍스트 정보 (SPF, DKIM, 인증 등)
예:
example.com. IN TXT "v=spf1 include:_spf.google.com ~all"
DNS 레코드의 캐시 유효 시간 (초 단위)
예:
www.example.com. 300 IN A 93.184.216.34
↑
TTL (5분)
5분 동안 캐시 사용, 이후 재조회
1. 브라우저 캐시:
- 짧은 시간 (수 분)
- 빠른 접근
2. OS 캐시:
- 시스템 레벨
- hosts 파일 우선
3. Recursive DNS 캐시:
- ISP 서버
- TTL 기간 동안 유지
장점:
- 빠른 응답
- DNS 서버 부하 감소
단점:
- IP 변경 시 전파 지연
- TTL 만료까지 대기
DNS는 도메인 이름을 IP 주소로 변환하는 시스템입니다.
조회 과정:
1. 로컬 캐시 확인
2. Recursive DNS (ISP)
3. Root DNS
4. TLD DNS (.com)
5. Authoritative DNS
6. IP 주소 반환
캐싱으로 빠른 응답,
TTL로 유효 시간 관리합니다.
DNS (Domain Name System):
역할:
- 도메인 이름을 IP 주소로 변환
- 인터넷의 전화번호부
- www.example.com → 93.184.216.34
조회 과정:
1. 브라우저 캐시 확인
2. OS 캐시 확인
3. Recursive DNS 서버 (ISP)
4. Root DNS 서버
- 13개 루트 서버 (A~M)
- TLD 서버 위치 안내
5. TLD DNS 서버
- .com, .net, .org 관리
- Authoritative 서버 안내
6. Authoritative DNS 서버
- 실제 IP 주소 보유
- 최종 응답
7. IP 주소 반환 및 캐싱
DNS 레코드:
- A: IPv4 주소
- AAAA: IPv6 주소
- CNAME: 별칭
- MX: 메일 서버
- NS: 네임서버
- TXT: 텍스트 정보
캐싱:
- TTL로 유효 시간 설정
- 브라우저 → OS → Recursive DNS
- 빠른 응답, 서버 부하 감소
보안:
- DNSSEC: DNS 응답 무결성 검증
- DNS over HTTPS (DoH): 암호화
- DNS over TLS (DoT): 암호화
성능 최적화:
- CDN DNS (Cloudflare, AWS Route 53)
- Anycast로 가까운 서버 응답
- 지역별 IP 반환 (Geo DNS)