Skip to content
On this page

安全头配置

安全头是 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 应用安全的重要组成部分:

  1. CSP - 防止 XSS 和内容注入攻击
  2. HSTS - 强制 HTTPS 使用
  3. X-Frame-Options - 防止点击劫持
  4. X-Content-Type-Options - 防止 MIME 嗅探
  5. X-XSS-Protection - 启用浏览器 XSS 过滤

实施时应遵循渐进式方法,从基本头开始,逐步增强安全性,并在不同环境中使用相应的配置。