Appearance
安全头配置
安全头是 HTTP 响应中的特殊头部,用于增强 Web 应用的安全性。通过正确配置安全头,可以防范多种常见的 Web 攻击。
常见安全头部
1. Content Security Policy (CSP)
CSP 是最重要的安全头之一,用于防止跨站脚本(XSS)、点击劫持和其他代码注入攻击。
javascript
// CSP 配置示例
const cspHeader = {
// 基础配置
defaultSrc: ["'self'"], // 默认只允许同源资源
scriptSrc: ["'self'", "'unsafe-inline'"], // 脚本来源
styleSrc: ["'self'", "'unsafe-inline'"], // 样式来源
imgSrc: ["'self'", "data:", "https:"], // 图片来源
fontSrc: ["'self'", "https:", "data:"], // 字体来源
connectSrc: ["'self'", "https://api.example.com"], // AJAX 请求来源
frameSrc: ["'none'"], // 禁止嵌入 iframe
objectSrc: ["'none'"], // 禁止插件
mediaSrc: ["'self'"], // 媒体来源
childSrc: ["'self'"], // Worker 和嵌入框架
frameAncestors: ["'none'"], // 防止点击劫持
formAction: ["'self'"], // 表单提交地址
baseUri: ["'self'"], // base 标签来源
manifestSrc: ["'self'"], // manifest 文件来源
workerSrc: ["'self'"] // Worker 脚本来源
};
// CSP 头部字符串格式
const cspString = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' https: data:",
"connect-src 'self' https://api.example.com",
"frame-src 'none'",
"object-src 'none'",
"frame-ancestors 'none'",
"form-action 'self'"
].join('; ');
// 在 Express.js 中设置
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', cspString);
next();
});
2. HTTP Strict Transport Security (HSTS)
强制浏览器只使用 HTTPS 连接。
javascript
// HSTS 配置
const hstsConfig = {
maxAge: 31536000, // 一年 (秒)
includeSubDomains: true, // 应用于所有子域名
preload: true // 允许加入浏览器预加载列表
};
const hstsHeader = `max-age=${hstsConfig.maxAge}; ` +
`${hstsConfig.includeSubDomains ? 'includeSubDomains; ' : ''}` +
`${hstsConfig.preload ? 'preload' : ''}`;
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', hstsHeader);
next();
});
3. X-Frame-Options
防止点击劫持攻击。
javascript
// X-Frame-Options 配置
app.use((req, res, next) => {
// DENY: 完全不允许嵌入
// SAMEORIGIN: 只允许同源嵌入
// ALLOW-FROM uri: 允许指定来源嵌入(已废弃)
res.setHeader('X-Frame-Options', 'DENY');
next();
});
4. X-Content-Type-Options
防止 MIME 类型嗅探。
javascript
// 防止浏览器忽略响应的 Content-Type
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
});
5. X-XSS-Protection
启用浏览器的 XSS 过滤器。
javascript
// XSS 保护配置
app.use((req, res, next) => {
// 1; mode=block: 启用过滤器并阻止页面加载
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
6. Referrer-Policy
控制 Referrer 头部的发送。
javascript
// Referrer 策略配置
const referrerPolicies = {
noReferrer: 'no-referrer', // 不发送 referrer
noReferrerWhenDowngrade: 'no-referrer-when-downgrade', // 默认值
sameOrigin: 'same-origin', // 同源时发送
origin: 'origin', // 发送源信息
strictOrigin: 'strict-origin', // 仅安全连接时发送源
originWhenCrossOrigin: 'origin-when-cross-origin', // 跨域时发送源
strictOriginWhenCrossOrigin: 'strict-origin-when-cross-origin', // 严格跨域
unsafeUrl: 'unsafe-url' // 发送完整 URL
};
app.use((req, res, next) => {
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
next();
});
使用 Helmet.js
Helmet.js 是一个流行的 Node.js 安全头中间件集合。
javascript
const helmet = require('helmet');
// 基础 Helmet 配置
app.use(helmet());
// 自定义 Helmet 配置
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https:", "data:"],
connectSrc: ["'self'", "https://api.example.com"],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
formAction: ["'self'"],
baseUri: ["'self'"]
},
reportOnly: false // 是否只报告不阻止
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
frameguard: {
action: 'deny' // 或 'sameorigin' 或 'allow-from'
},
referrerPolicy: {
policy: 'strict-origin-when-cross-origin'
}
}));
高级 CSP 配置
动态 CSP 策略
javascript
// 根据用户角色动态生成 CSP
function generateDynamicCsp(userRole) {
const policies = {
default: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"]
},
admin: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"], // 管理员可能需要
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:", "http:"], // 管理员可能需要 HTTP
connectSrc: ["'self'", "https://api.example.com", "ws:"]
},
contentEditor: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:", "https://trusted-cdn.com"]
}
};
return policies[userRole] || policies.default;
}
// 动态 CSP 中间件
app.use((req, res, next) => {
// 假设用户信息已在认证中间件中设置
const userRole = req.user?.role || 'default';
const cspPolicy = generateDynamicCsp(userRole);
const cspDirectives = Object.entries(cspPolicy)
.map(([directive, sources]) => {
const sourceStr = sources.map(src => `'${src}'`).join(' ');
return `${directive.replace(/([A-Z])/g, '-$1').toLowerCase()} ${sourceStr}`;
})
.join('; ');
res.setHeader('Content-Security-Policy', cspDirectives);
next();
});
CSP 报告机制
javascript
// CSP 违规报告处理
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
const report = req.body['csp-report'];
console.error('CSP Violation Report:', {
documentUri: report.document_uri,
violatedDirective: report.violated_directive,
originalPolicy: report.original_policy,
blockedUri: report.blocked_uri,
sourceFile: report.source_file,
lineNumber: report.line_number,
columnNumber: report.column_number,
timestamp: new Date().toISOString()
});
// 可以将违规报告存储到数据库或发送警报
res.status(204).end();
});
// 包含报告 URI 的 CSP
const cspWithReporting = [
"default-src 'self'",
"script-src 'self'",
"report-uri /csp-report",
"report-to csp-endpoint"
].join('; ');
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', cspWithReporting);
next();
});
框架特定配置
Express.js 安全头中间件
javascript
// 自定义安全头中间件
function securityHeaders(options = {}) {
const defaults = {
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
xFrameOptions: 'DENY',
xContentTypeOptions: 'nosniff',
xXssProtection: '1; mode=block',
referrerPolicy: 'strict-origin-when-cross-origin'
};
const config = { ...defaults, ...options };
return (req, res, next) => {
// HSTS
const hstsValue = `max-age=${config.hsts.maxAge}` +
(config.hsts.includeSubDomains ? '; includeSubDomains' : '') +
(config.hsts.preload ? '; preload' : '');
res.setHeader('Strict-Transport-Security', hstsValue);
// X-Frame-Options
res.setHeader('X-Frame-Options', config.xFrameOptions);
// X-Content-Type-Options
res.setHeader('X-Content-Type-Options', config.xContentTypeOptions);
// X-XSS-Protection
res.setHeader('X-XSS-Protection', config.xXssProtection);
// Referrer-Policy
res.setHeader('Referrer-Policy', config.referrerPolicy);
next();
};
}
// 使用自定义中间件
app.use(securityHeaders());
Next.js 安全头配置
javascript
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;"
},
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
}
]
}
];
}
};
安全头测试和验证
安全头检查工具
javascript
// 安全头验证中间件
function validateSecurityHeaders() {
return (req, res, next) => {
// 保存原始的 setHeader 方法
const originalSetHeader = res.setHeader;
res.setHeader = function(name, value) {
// 验证安全头的值
switch (name.toLowerCase()) {
case 'content-security-policy':
if (!validateCsp(value)) {
console.warn('Invalid CSP detected:', value);
}
break;
case 'strict-transport-security':
if (!validateHsts(value)) {
console.warn('Invalid HSTS detected:', value);
}
break;
case 'x-frame-options':
if (!validateXFrameOptions(value)) {
console.warn('Invalid X-Frame-Options detected:', value);
}
break;
}
return originalSetHeader.call(this, name, value);
};
next();
};
}
// CSP 验证函数
function validateCsp(cspString) {
try {
const directives = cspString.split(';');
for (const directive of directives) {
const [name, ...values] = directive.trim().split(/\s+/);
if (!name) continue;
// 验证指令名称是否有效
const validDirectives = [
'default-src', 'script-src', 'style-src', 'img-src', 'font-src',
'connect-src', 'frame-src', 'object-src', 'media-src', 'child-src',
'frame-ancestors', 'form-action', 'base-uri', 'manifest-src', 'worker-src'
];
if (!validDirectives.includes(name)) {
console.warn(`Invalid CSP directive: ${name}`);
return false;
}
}
return true;
} catch (error) {
console.error('CSP validation error:', error);
return false;
}
}
// HSTS 验证函数
function validateHsts(hstsString) {
const parts = hstsString.split(';');
let hasMaxAge = false;
for (const part of parts) {
const trimmed = part.trim();
if (trimmed.startsWith('max-age=')) {
const maxAge = parseInt(trimmed.split('=')[1]);
if (isNaN(maxAge) || maxAge <= 0) {
return false;
}
hasMaxAge = true;
} else if (!['includeSubDomains', 'preload'].includes(trimmed)) {
console.warn(`Invalid HSTS directive: ${trimmed}`);
return false;
}
}
return hasMaxAge;
}
// X-Frame-Options 验证函数
function validateXFrameOptions(xfoString) {
const validValues = ['DENY', 'SAMEORIGIN', 'ALLOW-FROM'];
return validValues.includes(xfoString.toUpperCase());
}
安全头监控
安全头合规性检查
javascript
// 安全头监控中间件
function securityHeadersMonitor() {
return (req, res, next) => {
const startTime = Date.now();
const originalEnd = res.end;
res.end = function(chunk, encoding, callback) {
// 记录响应时间
const duration = Date.now() - startTime;
// 检查是否设置了关键安全头
const securityHeadersPresent = {
csp: res.hasHeader('Content-Security-Policy'),
hsts: res.hasHeader('Strict-Transport-Security'),
xfo: res.hasHeader('X-Frame-Options'),
xcto: res.hasHeader('X-Content-Type-Options'),
xxss: res.hasHeader('X-XSS-Protection')
};
// 记录安全头状态
const allPresent = Object.values(securityHeadersPresent).every(Boolean);
if (!allPresent) {
console.warn('Missing security headers:', {
path: req.path,
method: req.method,
missing: Object.entries(securityHeadersPresent)
.filter(([header, present]) => !present)
.map(([header]) => header),
timestamp: new Date().toISOString()
});
}
return originalEnd.call(this, chunk, encoding, callback);
};
next();
};
}
app.use(securityHeadersMonitor());
安全头最佳实践
1. 分阶段实施
javascript
// 分阶段安全头实施
const securityPhase = process.env.SECURITY_PHASE || 'basic';
const securityHeadersByPhase = {
basic: [
{ name: 'X-Content-Type-Options', value: 'nosniff' },
{ name: 'X-Frame-Options', value: 'DENY' },
{ name: 'X-XSS-Protection', value: '1; mode=block' }
],
intermediate: [
{ name: 'X-Content-Type-Options', value: 'nosniff' },
{ name: 'X-Frame-Options', value: 'DENY' },
{ name: 'X-XSS-Protection', value: '1; mode=block' },
{ name: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' }
],
advanced: [
{ name: 'X-Content-Type-Options', value: 'nosniff' },
{ name: 'X-Frame-Options', value: 'DENY' },
{ name: 'X-XSS-Protection', value: '1; mode=block' },
{ name: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' },
{ name: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ name: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" }
]
};
app.use((req, res, next) => {
const headers = securityHeadersByPhase[securityPhase];
headers.forEach(header => {
res.setHeader(header.name, header.value);
});
next();
});
2. 环境特定配置
javascript
// 根据环境配置安全头
function getSecurityConfig(env = process.env.NODE_ENV) {
const configs = {
development: {
csp: "default-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' ws:",
hsts: null, // 开发环境不使用 HSTS
xfo: 'SAMEORIGIN' // 开发时可能需要 iframe
},
staging: {
csp: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;",
hsts: 'max-age=300; includeSubDomains', // 短时间测试
xfo: 'DENY'
},
production: {
csp: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https: data:;",
hsts: 'max-age=31536000; includeSubDomains; preload',
xfo: 'DENY'
}
};
return configs[env] || configs.production;
}
app.use((req, res, next) => {
const config = getSecurityConfig();
if (config.csp) res.setHeader('Content-Security-Policy', config.csp);
if (config.hsts) res.setHeader('Strict-Transport-Security', config.hsts);
if (config.xfo) res.setHeader('X-Frame-Options', config.xfo);
next();
});
安全头测试
使用自动化工具测试
javascript
// 安全头测试工具
async function testSecurityHeaders(url) {
try {
const response = await fetch(url);
const headers = Object.fromEntries(response.headers.entries());
const results = {
url,
headers: {},
issues: []
};
// 检查各个安全头
const securityTests = {
'Content-Security-Policy': {
required: true,
test: (value) => value && value.includes('default-src'),
message: 'CSP should be configured with default-src directive'
},
'Strict-Transport-Security': {
required: process.env.NODE_ENV === 'production',
test: (value) => value && value.includes('max-age='),
message: 'HSTS should have max-age directive'
},
'X-Frame-Options': {
required: true,
test: (value) => ['DENY', 'SAMEORIGIN'].includes(value.toUpperCase()),
message: 'X-Frame-Options should be DENY or SAMEORIGIN'
},
'X-Content-Type-Options': {
required: true,
test: (value) => value && value.toLowerCase() === 'nosniff',
message: 'X-Content-Type-Options should be nosniff'
},
'X-XSS-Protection': {
required: true,
test: (value) => value && value.startsWith('1'),
message: 'X-XSS-Protection should be enabled'
},
'Referrer-Policy': {
required: false,
test: (value) => value,
message: 'Referrer-Policy is recommended'
}
};
for (const [header, testConfig] of Object.entries(securityTests)) {
const value = headers[header];
const present = !!value;
const valid = present ? testConfig.test(value) : false;
results.headers[header] = {
present,
valid,
value: value || null
};
if (testConfig.required && !present) {
results.issues.push(`${header} header is missing`);
} else if (present && !valid) {
results.issues.push(testConfig.message);
}
}
return results;
} catch (error) {
return {
url,
error: error.message,
issues: [`Failed to test headers: ${error.message}`]
};
}
}
总结
安全头配置是 Web 应用安全的重要组成部分:
- CSP - 防止 XSS 和内容注入攻击
- HSTS - 强制 HTTPS 使用
- X-Frame-Options - 防止点击劫持
- X-Content-Type-Options - 防止 MIME 嗅探
- X-XSS-Protection - 启用浏览器 XSS 过滤
实施时应遵循渐进式方法,从基本头开始,逐步增强安全性,并在不同环境中使用相应的配置。