(시큐어코딩)Express 기반 Node.js 앱 보안 강화를 위한 핵심 기능

Node.js와 Express를 사용한 웹 앱 개발, 빠르게 만들 수 있는 만큼 보안이 허술하면 큰 사고로 이어질 수 있습니다. 지금 점검해보세요!

안녕하세요, 개발자 여러분! ICT리더 리치입니다. 백엔드 개발에서 빠르고 효율적인 프레임워크로 각광받는 Express.js, 하지만 그만큼 기본 보안 설정이 부족한 경우도 많죠. 실무에서 수많은 Node.js 프로젝트를 검토하면서 알게 된 사실은, 단 몇 줄의 코드만으로도 보안 수준이 크게 향상된다는 점입니다.

오늘은 Express 기반 Node.js 앱에서 꼭 적용해야 할 보안 기능 10가지를 집중적으로 소개드릴게요.

보안 코드가 출력된 화면을 바라보는 여성 개발자
Node.js Express 앱 보안 점검을 위한 대표 이미지

1. Helmet을 통한 HTTP 헤더 보안 강화

Helmet은 Express 앱에서 보안 관련 HTTP 헤더를 간단하게 설정할 수 있도록 도와주는 미들웨어입니다. 이 도구를 활용하면 XSS 보호, 콘텐츠 보안 정책, MIME sniffing 방지 등 다양한 보안 기능을 한 번에 적용할 수 있습니다. 특히 app.use(helmet()); 한 줄로 여러 가지 위험 요소를 차단할 수 있다는 것이 장점입니다.

Helmet은 Express 앱의 HTTP 응답 헤더를 강화하여, XSS, 클릭재킹 등의 보안 위협을 사전에 방지합니다.


// helmet 보안 미들웨어 설정 예시
const express = require('express');
const helmet = require('helmet');


const app = express();


// Helmet을 기본값으로 설정
app.use(helmet());


// Content Security Policy 적용 예시
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", 'trusted-cdn.com'],
},
})
);


// X-Frame-Options, HSTS, Referrer-Policy 등 자동 설정됨
// production 환경에서만 적용할 경우 조건문 사용
if (process.env.NODE_ENV === 'production') {
app.use(helmet());
}


app.get('/', (req, res) => {
res.send('🚀 보안 헤더 적용된 홈 페이지');
});


app.listen(3000, () => {
console.log('서버 실행 중 http://localhost:3000');
});

2. CORS 정책 설정의 중요성

CORS(Cross-Origin Resource Sharing)는 외부 도메인에서 리소스를 요청할 때 발생하는 보안 이슈를 방지하는 설정입니다. 무분별한 CORS 허용은 보안에 매우 취약하므로, 신뢰된 출처(origin)만 명시적으로 허용하는 것이 중요합니다.

항목 설명
Access-Control-Allow-Origin 요청을 허용할 출처(Origin) 지정
Access-Control-Allow-Methods 허용할 HTTP 메서드 설정(GET, POST 등)
Access-Control-Allow-Headers 인증을 위한 커스텀 헤더 포함 여부 설정

CORS(Cross-Origin Resource Sharing)는 외부 도메인에서 서버 리소스를 호출할 수 있도록 허용하는 설정입니다. 안전한 인증 및 도메인 제어를 위해 신중하게 구성해야 합니다.


const express = require('express');
const cors = require('cors');


const app = express();


const corsOptions = {
origin: ['https://example.com', 'https://admin.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
};


app.use(cors(corsOptions));


app.get('/api/data', (req, res) => {
res.json({ message: '📦 보안 설정된 API 응답입니다.' });
});


app.listen(4000, () => {
console.log('🚀 CORS 설정된 서버 실행 중 http://localhost:4000');
});

3. 사용자 입력 검증(Input Validation)

사용자 입력을 신뢰해서는 안 됩니다. express-validator 같은 라이브러리를 사용하면, 요청이 들어오기 전에 형식, 길이, 유효성 등을 체크하여 SQL Injection, XSS, 데이터 오류를 사전에 방지할 수 있습니다.

  • 이메일 형식 검증 (isEmail())
  • 공백 제거 및 트리밍 (trim())
  • 문자열 길이 제한 (isLength({ min: 5 }))

사용자 입력은 항상 신뢰할 수 없습니다. 유효성 검증과 정규화(Sanitization)를 통해 SQL Injection, XSS 등 다양한 공격을 예방할 수 있습니다.


const express = require('express');
const { body, validationResult } = require('express-validator');


const app = express();
app.use(express.json());


app.post('/register', [
// 입력값 검증
body('email').isEmail().withMessage('이메일 형식이 아닙니다.'),
body('password').isLength({ min: 8 }).withMessage('비밀번호는 최소 8자 이상'),
// 입력 정규화
body('email').normalizeEmail(),
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}


// 검증 통과 시 회원가입 로직 수행
const { email, password } = req.body;
console.log('회원가입 요청:', email);


res.status(200).json({ message: '✅ 회원가입 성공!' });
});


app.listen(5000, () => {
console.log('🛡️ 입력 검증 서버 실행 중');
});

4. 요청 제한(Rate Limiting)으로 DoS 방지

Express 앱에서 Rate Limiting은 DoS(Denial of Service) 공격을 방어하는 효과적인 전략입니다. express-rate-limit 라이브러리를 활용하면 IP당 요청 수를 제한할 수 있어, 과도한 트래픽이나 악의적 봇 접근을 차단할 수 있습니다.

기능 설명
windowMs 요청 제한 시간 단위 설정 (예: 15분)
max 해당 시간 동안 허용할 최대 요청 수
message 요청 초과 시 사용자에게 보낼 응답 메시지

Rate limiting은 클라이언트가 일정 시간 내에 요청할 수 있는 횟수를 제한하여, DoS(Denial of Service)나 무차별 공격을 방지하는 데 사용됩니다.


const express = require('express');
const rateLimit = require('express-rate-limit');


const app = express();


// 기본 요청 제한 설정 (IP당 15분에 최대 100번)
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15분
max: 100, // 최대 요청 수
message: '⚠️ 너무 많은 요청입니다. 잠시 후 다시 시도해주세요.',
standardHeaders: true, // RateLimit-* 헤더 추가
legacyHeaders: false, // X-RateLimit-* 헤더 제거
});


// 모든 요청에 제한 적용
app.use(limiter);


app.get('/', (req, res) => {
res.send('📈 요청 제한이 적용된 안전한 서버입니다.');
});


app.listen(3000, () => {
console.log('🛡️ Rate limiting 적용 서버 실행 중');
});
Node.js Express 보안을 설정 중인 여성 개발자 인포그래픽
Express 보안 기능 핵심 가이드 – 여성 개발자 중심 인포그래픽

세션 기반 인증을 사용하는 경우 쿠키 설정이 매우 중요합니다. HttpOnly 속성은 자바스크립트에서 쿠키에 접근하지 못하게 하여 XSS 공격을 방지하며, Secure 속성은 HTTPS 연결에서만 쿠키가 전송되도록 제한합니다.

  1. Secure: true → HTTPS에서만 쿠키 전송
  2. HttpOnly: true → JS 접근 차단
  3. SameSite: 'strict' → CSRF 방지 강화

JWT(JSON Web Token)는 인증 정보를 안전하게 전달하기 위한 방식입니다. 발급 시 서명(secret key)과 만료시간 설정이 필수이며, 토큰 검증도 반드시 수행해야 합니다.


const express = require('express');
const jwt = require('jsonwebtoken');


const app = express();
app.use(express.json());


const secretKey = 'super_secret_key';


// 로그인 후 토큰 발급
app.post('/login', (req, res) => {
const user = { id: 1, name: '홍길동' };
const token = jwt.sign(user, secretKey, { expiresIn: '1h' });
res.json({ token });
});


// JWT 검증 미들웨어
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);


jwt.verify(token, secretKey, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}


// 보호된 라우트
app.get('/secure', authenticateToken, (req, res) => {
res.json({ message: `🔐 ${req.user.name}님, 보호된 정보입니다.` });
});


app.listen(5001, () => {
console.log('JWT 인증 서버 실행 중');
});

6. JWT 인증의 안전한 사용법

JWT(Json Web Token)는 토큰 기반 인증 시스템에서 많이 사용되며, 그만큼 보안상 신중한 설정이 필요합니다. 토큰은 반드시 서명(Signature)이 적용되어야 하며, 만료 시간(exp)발급자(issuer) 설정도 권장됩니다.

  • HS256, RS256 알고리즘 중 하나로 서명 필수
  • 토큰은 Authorization: Bearer 형식으로 전달
  • 발급 후 1시간 이내 만료 설정 권장

HTTPS는 전송 중 데이터를 암호화하여 도청이나 중간자 공격을 방지합니다. 인증서 설정 후 HTTPS 서버를 구성하는 것이 필수입니다.


const https = require('https');
const fs = require('fs');
const express = require('express');


const app = express();


// SSL 인증서 및 키 파일 경로
const options = {
key: fs.readFileSync('./ssl/server.key'),
cert: fs.readFileSync('./ssl/server.cert')
};


app.get('/', (req, res) => {
res.send('🔒 HTTPS로 안전하게 통신 중입니다.');
});


https.createServer(options, app).listen(443, () => {
console.log('✅ HTTPS 서버 실행 중 https://localhost');
});

7. XSS 방지를 위한 출력 이스케이프

크로스 사이트 스크립팅(XSS)은 사용자 입력값을 그대로 HTML에 출력할 때 발생합니다. 이를 방지하려면 반드시 출력 이스케이프(Escape) 처리를 적용해야 합니다. 템플릿 엔진(ejs, pug 등)에서 제공하는 escape 기능을 사용하거나, lodash.escape(), he 라이브러리를 활용할 수 있습니다.

XSS(Cross-site Scripting) 공격은 악의적인 스크립트가 사용자 브라우저에서 실행되는 것을 의미합니다. 출력 시 특수문자를 HTML 엔티티로 이스케이프 처리해야 합니다.


const express = require('express');
const xss = require('xss');


const app = express();
app.use(express.urlencoded({ extended: true }));


app.post('/comment', (req, res) => {
// 사용자 입력을 XSS 필터링
const rawComment = req.body.comment;
const safeComment = xss(rawComment);


// DB에 저장하거나 출력할 때 안전하게 처리
res.send(`

🛡️ 안전한 댓글: ${safeComment}

`); }); app.listen(8080, () => { console.log('XSS 필터링 서버 실행 중'); });

8. NoSQL Injection 방지: mongo-sanitize

MongoDB 같은 NoSQL 데이터베이스는 특수한 쿼리 문법으로 인해 NoSQL Injection 위험이 있습니다. 대표적으로 $gt, $ne 등의 연산자를 악용하는 방식입니다. mongo-sanitize 라이브러리를 사용하면, 이러한 특수 키를 필터링하여 악성 입력을 차단할 수 있습니다.

mongo-sanitize는 MongoDB에서 사용하는 쿼리 연산자($ne, $gt 등)를 무력화시켜 NoSQL Injection 공격을 방어합니다. 입력값에 직접 쿼리 객체를 삽입하는 공격을 방지합니다.


const express = require('express');
const sanitize = require('mongo-sanitize');
const mongoose = require('mongoose');


const app = express();
app.use(express.json());


// MongoDB 연결 예시
mongoose.connect('mongodb://localhost:27017/securedb');


const User = mongoose.model('User', new mongoose.Schema({ email: String }));


app.post('/find-user', async (req, res) => {
const cleanInput = sanitize(req.body);
try {
const user = await User.findOne({ email: cleanInput.email });
res.json(user || { message: '사용자를 찾을 수 없습니다.' });
} catch (err) {
res.status(500).json({ error: '쿼리 오류 발생' });
}
});


app.listen(3002, () => {
console.log('NoSQL Injection 방지 서버 실행 중');
});

9. 안전한 에러 핸들링 전략

에러 메시지에는 시스템 구조나 민감한 정보가 노출될 수 있습니다. 개발 환경에서는 상세 에러 로그를 출력하고, 프로덕션에서는 일반화된 메시지로 대체하는 것이 원칙입니다. 또한 모든 try...catch문이나 미들웨어를 통해 예외를 포착하고, 로그 시스템으로 기록해야 합니다.

에러 메시지에 스택 추적, DB 정보 등 민감한 정보를 노출하지 않도록 하고, 사용자에게는 일반적인 메시지를 보여주며 서버 로그에는 상세 에러를 남겨야 합니다.


const express = require('express');
const app = express();


app.get('/error', (req, res, next) => {
try {
// 일부러 오류 발생
throw new Error('DB 연결 실패');
} catch (err) {
next(err); // 오류 핸들러로 전달
}
});


// 전역 에러 핸들러
app.use((err, req, res, next) => {
console.error('❌ 서버 오류:', err.stack); // 서버에만 로그
res.status(500).json({
message: '서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'
});
});


app.listen(3003, () => {
console.log('에러 핸들링 서버 실행 중');
});

10. 프로덕션 배포 시 꼭 점검할 보안 항목

서비스 배포 전에는 반드시 보안 점검 항목을 리스트업해야 합니다. 다음은 대표적인 점검 리스트입니다:

  • 환경변수(Env) 암호화 및 dotenv 활용
  • 에러 로그와 인증 키 분리 관리
  • HTTP → HTTPS 리디렉션 설정
  • 패키지 의존성 보안 점검(npm audit)
  • 서버 방화벽, 포트 제한, SSH 접근 관리

프로덕션 환경에서 Express 앱을 배포할 때는 민감 정보 보호, 포트 제한, 디버깅 제거 등 사전 점검이 필수입니다. 다음 코드는 환경별 설정 적용 예시입니다.


const express = require('express');
require('dotenv').config();


const app = express();


if (process.env.NODE_ENV === 'production') {
app.set('trust proxy', 1); // Heroku 등 프록시 신뢰
app.disable('x-powered-by'); // Express 노출 방지
console.log('🚀 프로덕션 모드로 실행');
} else {
console.log('🧪 개발 모드로 실행');
}


app.get('/', (req, res) => {
res.send('안전하게 배포된 서버입니다.');
});


app.listen(process.env.PORT || 3000, () => {
console.log(`서버 실행 중: ${process.env.NODE_ENV}`);
});
Express 앱 보안을 위한 핵심 기능을 소개하는 여성 개발자
 Node.js Express 앱 보안, 지금 바로 점검하세요

11. 자주 묻는 질문 (FAQ)

Q Helmet을 사용하면 모든 보안 이슈를 막을 수 있나요?

Helmet은 보안 강화에 도움이 되지만 만능은 아닙니다. 다양한 보안 기능을 함께 적용해야 실질적인 보호가 가능합니다.

Q CORS 설정은 모든 도메인에 대해 허용해도 되나요?

아니요. 가능한 경우, 신뢰된 origin만 허용하는 것이 안전합니다. 전체 허용(*)은 보안 취약점을 초래할 수 있습니다.

Q JWT는 로컬스토리지에 저장해도 안전한가요?

로컬스토리지는 XSS에 취약하므로 권장하지 않습니다. 가능한 경우 HttpOnly 쿠키로 설정하는 것이 안전합니다.

Q Input Validation은 어느 위치에서 수행하나요?

Express의 미들웨어에서, 라우터 핸들러 전에 수행하는 것이 일반적입니다. 이로 인해 클린 코드 유지와 보안이 동시에 가능합니다.

Q Express 외에 Nest.js도 보안 기능이 비슷한가요?

네, Nest.js는 Express를 내부에 포함하고 있어 대부분의 보안 미들웨어나 설정을 동일하게 사용할 수 있습니다.

12. 마무리 요약

✅ Express 보안 기능, 선택이 아닌 필수입니다.

오늘 소개한 10가지 보안 기능은 Express 기반 Node.js 앱을 실무에 안전하게 배포하기 위해 반드시 필요한 요소들입니다.
단순한 보안 설정부터 고급 인증 방식까지, 모든 부분이 실제 공격을 예방하고, 신뢰할 수 있는 서비스를 만드는 기초가 됩니다.

특히 Helmet, CORS, Input Validation, Rate Limiting, JWT 보안 등은 실시간 공격 시나리오에서 매우 유효한 방어 전략이니 꼭 적용해보시길 권장합니다.

"Express 보안을 미루지 마세요. 적용하는 그 순간부터, 여러분의 서버는 훨씬 더 단단해집니다."

댓글

이 블로그의 인기 게시물

React, Vue, Angular 비교 분석 – 내 프로젝트에 가장 적합한 JS 프레임워크는?

2025년 AI 트렌드 완전정리: 당신이 놓치면 안 되는 기술 7가지