Node.js 교재 · 24편 · 보안

보안 기초 — helmet·cors·rate-limit

세 라이브러리 한 줄씩 추가하면 기본 공격 80% 가 차단된다.

보안 방패가 악성 요청을 막는 컨셉 일러스트

인증·DB·검증은 앞에서 다뤘다. 그 외 HTTP 단에서 막아야 할 공격이 또 한 묶음 — XSS·clickjacking·MIME sniffing·CSRF·CORS 우회·무차별 대입·DDoS. 다행히 한 줄 라이브러리들이 표준으로 다 해결.

이번 편은 Express 백엔드의 기본 방어 4종 — helmet·cors·rate-limit·HTTPS 강제. 그 위 응용은 OWASP Top 10 별 정식 가이드.

1. helmet — HTTP 보안 헤더 12종 한 줄

브라우저에 "이 사이트는 안전한 정책으로 동작합니다" 를 알려주는 HTTP 헤더들. 직접 다 적으면 30줄, helmet 으로 한 줄.

$ npm install helmet
import helmet from 'helmet'; app.use(helmet());

이 한 줄이 적용하는 헤더:

헤더막는 것
Content-Security-PolicyXSS, 외부 스크립트 주입
X-Frame-Optionsclickjacking (iframe 으로 사이트 가리기)
X-Content-Type-OptionsMIME sniffing (.txt 가 .js 로 실행)
Strict-Transport-SecurityHTTPS 강제 (HSTS)
Referrer-Policyreferrer 누출 제한
X-DNS-Prefetch-ControlDNS 사전 조회 차단

CSP 가 가장 강력하지만 가장 까다로움 — 인라인 스크립트·외부 CDN 쓰면 정책 풀어야. 첫 적용 시 브라우저 콘솔 보면서 정책 조정.

2. cors — origin 허용 목록 정확히

14편의 직접 구현 대신 표준 패키지.

$ npm install cors
import cors from 'cors'; // ❌ 모든 origin 허용 — 데모용, production 금지 app.use(cors()); // ✅ 명시적 허용 목록 app.use(cors({ origin: ['https://junai.ai', 'https://www.junai.ai', 'http://localhost:3000'], credentials: true, // 쿠키 허용 methods: ['GET', 'POST', 'PUT', 'DELETE'], }));
최악의 사고app.use(cors()) 만 적으면 모든 origin 허용. 즉 누구나 어디서나 본인 API 를 부를 수 있다. CSRF·신용카드 결제 사고 1위. 반드시 명시 origin 배열. 와일드카드 (*) + credentials: true 는 브라우저가 거부하지만, 그래도 명시 안 하는 것 자체가 위험.

3. express-rate-limit — 무차별 대입·봇 차단

1초에 1만 번 로그인 시도하는 봇 차단. 한 IP 의 요청을 시간당 N회로 제한.

$ npm install express-rate-limit
import rateLimit from 'express-rate-limit'; // 전역 — 일반 API const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15분 limit: 100, // IP 당 100 요청 standardHeaders: 'draft-7', legacyHeaders: false, }); app.use('/api/', apiLimiter); // 인증 라우트 — 더 빡빡 const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, limit: 5, // 15분 5회만 skipSuccessfulRequests: true, // 성공은 카운트 안 함 }); app.use('/api/auth/login', authLimiter);

로그인은 실패만 카운트(skipSuccessfulRequests) — 정상 사용자는 영향 없고 무차별 대입만 차단. 5분 5회 패턴이 표준.

Redis backed 권장 — 기본 메모리 저장은 cluster·여러 인스턴스 환경에서 안 맞음(각 인스턴스가 다른 카운트). production 은 rate-limit-redis 로 Redis 공유 저장소. 23편 cluster + 24편 Redis 의 결합.

4. HTTPS 강제 — production 필수

HTTP 로 비밀번호 보내면 카페 와이파이에서 도청. production 은 무조건 HTTPS. Vercel·Cloudflare 같은 호스팅은 자동, 자체 서버는 nginx + Let's Encrypt.

그래도 누가 HTTP 로 접속하면 — 리디렉트:

// 프록시 뒤에서 (Vercel·nginx 일반) app.set('trust proxy', 1); app.use((req, res, next) => { if ( process.env.NODE_ENV === 'production' && req.headers['x-forwarded-proto'] !== 'https' ) { return res.redirect(301, `https://${req.hostname}${req.url}`); } next(); });

helmet 의 Strict-Transport-Security 가 같이 켜져 있어야 진정한 HTTPS 강제 — 한 번 https 접속한 브라우저가 그 도메인을 영원히 https 로만 기억.

5. 통합 표준 세팅

실전 production 서버의 시작 부분 — 한 모듈로 묶어둠.

// security.js import helmet from 'helmet'; import cors from 'cors'; import rateLimit from 'express-rate-limit'; export function applySecurity(app, env) { app.set('trust proxy', 1); app.use(helmet({ contentSecurityPolicy: env.NODE_ENV === 'production', })); app.use(cors({ origin: env.ALLOWED_ORIGINS.split(','), credentials: true, })); app.use('/api/', rateLimit({ windowMs: 15 * 60 * 1000, limit: 100, })); app.use('/api/auth/', rateLimit({ windowMs: 15 * 60 * 1000, limit: 5, skipSuccessfulRequests: true, })); } // server.js import { applySecurity } from './security.js'; applySecurity(app, env);

OWASP Top 10 의 6~7개 가 이 한 모듈로 자동 차단. 나머지(SQL Injection·XSS·인증·민감 정보 노출 등)는 앞 챕터들에서 커버. 보안 사고 신문에 안 나는 게 첫 목표.

요약 — 24편 좌표

여기까지 정리. 4종 표준 방어 — helmet(HTTP 보안 헤더 12종), cors(명시 origin 배열, 와일드카드 금지), express-rate-limit(15분 100회 일반, 5회 인증), HTTPS 리디렉트 + HSTS. applySecurity() 한 모듈로 묶어 production 서버 시작 시 일괄 적용. CSP 는 가장 강력하지만 가장 까다로움. 다음 편(마지막 둘 중 첫째)에서 배포 — PM2·Docker.

다음 편 예고 — 배포 PM2·Docker

PM2 프로세스 매니저, Docker 컨테이너화. 25편.

© 2026 주나이테크(주) @JUNAITECH