セキュリティヘッダーとは
セキュリティヘッダーは、ブラウザにセキュリティポリシーを伝えるHTTPレスポンスヘッダーです。XSS、クリックジャッキング、MIMEスニッフィングなどの攻撃を防ぎます。
主要なセキュリティヘッダー
Content-Security-Policy (CSP)
XSSやデータインジェクション攻撃を防止します。
Content-Security-Policy: default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
Strict-Transport-Security (HSTS)
HTTPSの使用を強制します。
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options
MIMEタイプスニッフィングを防止します。
X-Content-Type-Options: nosniff
X-Frame-Options
クリックジャッキングを防止します。
X-Frame-Options: DENY
# または
X-Frame-Options: SAMEORIGIN
Permissions-Policy
ブラウザ機能へのアクセスを制御します。
Permissions-Policy: camera=(), microphone=(), geolocation=(self)
Referrer-Policy
リファラー情報の送信を制御します。
Referrer-Policy: strict-origin-when-cross-origin
Next.js での実装
next.config.js
// next.config.js
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
},
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
`.replace(/\s{2,}/g, ' ').trim()
}
];
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders,
},
];
},
};
Express での実装
Helmet ライブラリ
// app.ts
import express from 'express';
import helmet from 'helmet';
const app = express();
// 基本的なセキュリティヘッダー
app.use(helmet());
// カスタム設定
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
frameAncestors: ["'none'"],
},
},
hsts: {
maxAge: 63072000,
includeSubDomains: true,
preload: true,
},
})
);
手動設定
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
next();
});
Nginx での設定
# /etc/nginx/conf.d/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';" always;
Vercel での設定
// vercel.json
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-XSS-Protection", "value": "1; mode=block" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}
CORS設定
// Express CORS
import cors from 'cors';
app.use(cors({
origin: ['https://example.com', 'https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400, // 24時間
}));
# Nginx CORS
location /api {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
}
セキュリティチェック
オンラインツール
# SecurityHeaders.com
# https://securityheaders.com/?q=example.com
# Mozilla Observatory
# https://observatory.mozilla.org/
curlでの確認
curl -I https://example.com
# 特定ヘッダーの確認
curl -I https://example.com | grep -i "content-security-policy"
curl -I https://example.com | grep -i "strict-transport"
CSP違反のレポート
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
// CSP違反レポートの受信
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
console.log('CSP Violation:', req.body['csp-report']);
res.status(204).end();
});
ベストプラクティス
1. 段階的な導入
# まずReport-Onlyで影響を確認
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
# 問題なければ本番適用
Content-Security-Policy: default-src 'self'
2. 環境ごとの設定
const isDev = process.env.NODE_ENV === 'development';
const cspDirectives = {
defaultSrc: ["'self'"],
scriptSrc: isDev
? ["'self'", "'unsafe-eval'", "'unsafe-inline'"]
: ["'self'"],
// ...
};
関連記事
- APIセキュリティ - API保護
- SSL証明書設定 - HTTPS設定
- ゼロトラストアーキテクチャ - セキュリティ設計
まとめ
セキュリティヘッダーは、Webアプリケーションの防御層として重要です。CSPは段階的に導入し、定期的にセキュリティスキャンを実施しましょう。
← Back to list