Express.js helmet 보안 헤더 설정 완벽 가이드
Express.js에서 helmet 미들웨어를 사용하여 보안 HTTP 헤더를 설정하는 방법을 다룹니다.
Introduction
웹 애플리케이션의 보안을 강화하기 위해 HTTP 보안 헤더를 설정하는 것은 필수적입니다. Express.js에서 helmet 미들웨어는 다양한 보안 헤더를 쉽게 설정할 수 있게 해줍니다.
Environment
# 프로젝트 의존성
npm list express helmet
# express@4.18.2
# helmet@7.1.0Problem
현재 앱에 보안 헤더가 설정되어 있지 않아 various 보안 문제가 발생했습니다:
# 보안 검사 결과
$ security-headers-cli check https://myapp.com
- Content-Security-Policy: Not set
- X-Content-Type-Options: Not set
- X-Frame-Options: Not set
- Strict-Transport-Security: Not set
- X-XSS-Protection: Not set
Grade: FAnalysis
보안 헤더 중요성
| 헤더 | 목적 | 필수도 |
|---|---|---|
| CSP | XSS 공격 방지 | 높음 |
| HSTS | HTTPS 강제 | 높음 |
| X-Content-Type-Options | MIME 스니핑 방지 | 중간 |
| X-Frame-Options | 클릭재킹 방지 | 중간 |
| Referrer-Policy | 정보 유출 방지 | 중간 |
Solution
1. helmet 기본 설정
const express = require('express');
const helmet = require('helmet');
const app = express();
// helmet 기본 설정 적용
app.use(helmet());
// 또는 개별 설정
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
connectSrc: ["'self'", "https://api.myapp.com"],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
},
}));2. 상세 보안 헤더 설정
const express = require('express');
const helmet = require('helmet');
const app = express();
// 종합 보안 헤더 설정
app.use(helmet());
// Content Security Policy 설정
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
"'unsafe-inline'",
"'unsafe-eval'",
"https://cdn.jsdelivr.net",
"https://unpkg.com",
],
styleSrc: [
"'self'",
"'unsafe-inline'",
"https://fonts.googleapis.com",
"https://cdn.jsdelivr.net",
],
imgSrc: [
"'self'",
"data:",
"https:",
"blob:",
],
fontSrc: [
"'self'",
"https://fonts.gstatic.com",
"https://cdn.jsdelivr.net",
],
connectSrc: [
"'self'",
"https://api.myapp.com",
"wss://ws.myapp.com",
],
mediaSrc: ["'self'"],
objectSrc: ["'none'"],
frameSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
upgradeInsecureRequests: [],
},
}));
// HSTS 설정
app.use(helmet.hsts({
maxAge: 31536000, // 1년
includeSubDomains: true,
preload: true,
}));
// X-Content-Type-Options
app.use(helmet.noSniff());
// X-Frame-Options
app.use(helmet.frameguard({ action: 'deny' }));
// X-XSS-Protection (레거시 브라우저)
app.use(helmet.xssFilter());
// Referrer Policy
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }));
// Permissions Policy
app.use(helmet.permissionsPolicy({
features: {
camera: [],
microphone: [],
geolocation: [],
payment: [],
},
}));3. 환경별 설정
const helmetConfig = {
development: {
contentSecurityPolicy: false, // 개발 시 비활성화
hsts: false,
},
production: {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.myapp.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
},
};
const env = process.env.NODE_ENV || 'development';
app.use(helmet(helmetConfig[env]));4. CSP 리포팅 설정
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
reportUri: '/csp-report',
reportTo: 'csp-endpoint',
},
}));
// CSP 리포트 엔드포인트
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
const report = req.body['csp-report'];
console.error('CSP Violation:', {
'blocked-uri': report['blocked-uri'],
'violated-directive': report['violated-directive'],
'effective-directive': report['effective-directive'],
'original-policy': report['original-policy'],
});
res.status(204).end();
});5. 커스텀 헤더 추가
// 커스텀 보안 헤더
app.use((req, res, next) => {
// API 키 보호
res.setHeader('X-Api-Version', '1.0');
// 캐시 제어
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
res.setHeader('Pragma', 'no-cache');
// 추가 보안 헤더
res.setHeader('X-Permitted-Cross-Domain-Policies', 'none');
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
next();
});Lessons Learned
- CSP 설정: XSS 공격 방지를 위해 Content-Security-Policy를 반드시 설정하세요
- HSTS 적용: HTTPS를 사용하는 경우 HSTS를 설정하여 보안을 강화하세요
- 환경별 설정: 개발과 프로덕션 환경에 따라 보안 헤더를 다르게 설정하세요
- CSP 리포팅: CSP 위반 사항을 모니터링하여 보안 문제를 조기에 발견하세요
- 정기 검사:
security-headers-cli와 같은 도구로 보안 헤더를 정기적으로 검사하세요
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.