发布于

前端安全防护实践:XSS、CSRF防护与内容安全策略

作者

前端安全防护实践:XSS、CSRF防护与内容安全策略

前端安全是Web应用的重要基石,本文将分享前端安全防护的实战经验,涵盖常见攻击手段的防护策略和最佳实践。

XSS攻击防护

XSS攻击类型与防护

// XSS防护工具类
class XSSProtection {
  constructor() {
    this.htmlEntities = {
      '&': '&',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#x27;',
      '/': '&#x2F;',
      '`': '&#x60;',
      '=': '&#x3D;'
    };
  }
  
  // HTML实体编码
  escapeHtml(str) {
    if (typeof str !== 'string') return str;
    
    return str.replace(/[&<>"'`=\/]/g, (match) => {
      return this.htmlEntities[match];
    });
  }
  
  // 属性值编码
  escapeAttribute(str) {
    if (typeof str !== 'string') return str;
    
    return str.replace(/[&<>"']/g, (match) => {
      return this.htmlEntities[match];
    });
  }
  
  // JavaScript字符串编码
  escapeJS(str) {
    if (typeof str !== 'string') return str;
    
    return str.replace(/[\\'"<>&\r\n\t]/g, (match) => {
      const escapeMap = {
        '\\': '\\\\',
        "'": "\\'",
        '"': '\\"',
        '<': '\\u003c',
        '>': '\\u003e',
        '&': '\\u0026',
        '\r': '\\r',
        '\n': '\\n',
        '\t': '\\t'
      };
      return escapeMap[match];
    });
  }
  
  // URL编码
  escapeURL(str) {
    if (typeof str !== 'string') return str;
    
    return encodeURIComponent(str);
  }
  
  // CSS编码
  escapeCSS(str) {
    if (typeof str !== 'string') return str;
    
    return str.replace(/[<>"'&\\\r\n]/g, (match) => {
      return '\\' + match.charCodeAt(0).toString(16) + ' ';
    });
  }
  
  // 安全的innerHTML替代方案
  safeSetHTML(element, html) {
    // 创建临时元素进行清理
    const temp = document.createElement('div');
    temp.innerHTML = this.escapeHtml(html);
    
    // 移除所有脚本标签
    const scripts = temp.querySelectorAll('script');
    scripts.forEach(script => script.remove());
    
    // 移除危险属性
    const dangerousAttrs = ['onclick', 'onload', 'onerror', 'onmouseover'];
    const allElements = temp.querySelectorAll('*');
    
    allElements.forEach(el => {
      dangerousAttrs.forEach(attr => {
        if (el.hasAttribute(attr)) {
          el.removeAttribute(attr);
        }
      });
      
      // 检查href和src属性
      if (el.hasAttribute('href')) {
        const href = el.getAttribute('href');
        if (href.startsWith('javascript:')) {
          el.removeAttribute('href');
        }
      }
      
      if (el.hasAttribute('src')) {
        const src = el.getAttribute('src');
        if (src.startsWith('javascript:')) {
          el.removeAttribute('src');
        }
      }
    });
    
    element.innerHTML = temp.innerHTML;
  }
  
  // 验证URL安全性
  isValidURL(url) {
    try {
      const urlObj = new URL(url);
      const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
      
      if (!allowedProtocols.includes(urlObj.protocol)) {
        return false;
      }
      
      // 检查是否为恶意域名
      const maliciousDomains = ['evil.com', 'malware.net'];
      if (maliciousDomains.includes(urlObj.hostname)) {
        return false;
      }
      
      return true;
    } catch (error) {
      return false;
    }
  }
  
  // 安全的动态脚本加载
  loadScript(src, options = {}) {
    return new Promise((resolve, reject) => {
      if (!this.isValidURL(src)) {
        reject(new Error('Invalid script URL'));
        return;
      }
      
      const script = document.createElement('script');
      script.src = src;
      script.async = options.async !== false;
      script.defer = options.defer || false;
      
      // 添加完整性检查
      if (options.integrity) {
        script.integrity = options.integrity;
        script.crossOrigin = 'anonymous';
      }
      
      script.onload = () => resolve(script);
      script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
      
      document.head.appendChild(script);
    });
  }
}

// 全局XSS防护实例
const xssProtection = new XSSProtection();

// React组件中的安全实践
import React from 'react';
import DOMPurify from 'dompurify';

function SafeComponent({ userContent, userUrl, userName }) {
  // 安全显示用户内容
  const sanitizedContent = DOMPurify.sanitize(userContent, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u'],
    ALLOWED_ATTR: []
  });
  
  // 安全显示用户名
  const safeName = xssProtection.escapeHtml(userName);
  
  // 安全处理用户URL
  const safeUrl = xssProtection.isValidURL(userUrl) ? userUrl : '#';
  
  return (
    <div>
      <h3>{safeName}</h3>
      <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
      <a href={safeUrl} target="_blank" rel="noopener noreferrer">
        访问链接
      </a>
    </div>
  );
}

输入验证与过滤

// 输入验证工具类
class InputValidator {
  constructor() {
    this.patterns = {
      email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
      phone: /^[\d\-\+\(\)\s]+$/,
      alphanumeric: /^[a-zA-Z0-9]+$/,
      safeString: /^[a-zA-Z0-9\s\-_.,!?]+$/,
    };
  }
  
  // 验证邮箱
  validateEmail(email) {
    if (!email || typeof email !== 'string') return false;
    
    // 长度检查
    if (email.length > 254) return false;
    
    // 格式检查
    if (!this.patterns.email.test(email)) return false;
    
    // 域名白名单检查
    const allowedDomains = ['gmail.com', 'outlook.com', 'company.com'];
    const domain = email.split('@')[1];
    
    if (allowedDomains.length > 0 && !allowedDomains.includes(domain)) {
      return false;
    }
    
    return true;
  }
  
  // 验证用户名
  validateUsername(username) {
    if (!username || typeof username !== 'string') return false;
    
    // 长度检查
    if (username.length < 3 || username.length > 20) return false;
    
    // 字符检查
    if (!this.patterns.alphanumeric.test(username)) return false;
    
    // 保留词检查
    const reservedWords = ['admin', 'root', 'system', 'null', 'undefined'];
    if (reservedWords.includes(username.toLowerCase())) return false;
    
    return true;
  }
  
  // 验证密码强度
  validatePassword(password) {
    if (!password || typeof password !== 'string') {
      return { valid: false, message: '密码不能为空' };
    }
    
    if (password.length < 8) {
      return { valid: false, message: '密码长度至少8位' };
    }
    
    if (password.length > 128) {
      return { valid: false, message: '密码长度不能超过128位' };
    }
    
    const hasLower = /[a-z]/.test(password);
    const hasUpper = /[A-Z]/.test(password);
    const hasNumber = /\d/.test(password);
    const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
    
    const strength = [hasLower, hasUpper, hasNumber, hasSpecial].filter(Boolean).length;
    
    if (strength < 3) {
      return { 
        valid: false, 
        message: '密码必须包含大写字母、小写字母、数字和特殊字符中的至少3种' 
      };
    }
    
    // 检查常见弱密码
    const weakPasswords = ['password', '123456', 'qwerty', 'admin'];
    if (weakPasswords.includes(password.toLowerCase())) {
      return { valid: false, message: '密码过于简单' };
    }
    
    return { valid: true, strength };
  }
  
  // 通用输入清理
  sanitizeInput(input, type = 'text') {
    if (typeof input !== 'string') return '';
    
    // 移除控制字符
    input = input.replace(/[\x00-\x1F\x7F]/g, '');
    
    // 限制长度
    const maxLengths = {
      text: 1000,
      name: 50,
      email: 254,
      url: 2048,
      comment: 5000
    };
    
    const maxLength = maxLengths[type] || maxLengths.text;
    if (input.length > maxLength) {
      input = input.substring(0, maxLength);
    }
    
    // 根据类型进行特定清理
    switch (type) {
      case 'name':
        input = input.replace(/[^a-zA-Z\s\-']/g, '');
        break;
      case 'alphanumeric':
        input = input.replace(/[^a-zA-Z0-9]/g, '');
        break;
      case 'numeric':
        input = input.replace(/[^0-9]/g, '');
        break;
      case 'url':
        // URL特殊处理
        try {
          new URL(input);
        } catch {
          input = '';
        }
        break;
    }
    
    return input.trim();
  }
  
  // 文件上传验证
  validateFile(file, options = {}) {
    const {
      maxSize = 5 * 1024 * 1024, // 5MB
      allowedTypes = ['image/jpeg', 'image/png', 'image/gif'],
      allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif']
    } = options;
    
    // 文件大小检查
    if (file.size > maxSize) {
      return { valid: false, message: '文件大小超出限制' };
    }
    
    // 文件类型检查
    if (!allowedTypes.includes(file.type)) {
      return { valid: false, message: '不支持的文件类型' };
    }
    
    // 文件扩展名检查
    const extension = '.' + file.name.split('.').pop().toLowerCase();
    if (!allowedExtensions.includes(extension)) {
      return { valid: false, message: '不支持的文件扩展名' };
    }
    
    // 文件名安全检查
    const safeName = file.name.replace(/[^a-zA-Z0-9.-]/g, '_');
    if (safeName !== file.name) {
      return { 
        valid: true, 
        message: '文件名包含特殊字符,已自动处理',
        safeName 
      };
    }
    
    return { valid: true };
  }
}

// 全局输入验证实例
const inputValidator = new InputValidator();

CSRF攻击防护

CSRF Token实现

// CSRF防护工具类
class CSRFProtection {
  constructor() {
    this.tokenName = 'csrf_token';
    this.headerName = 'X-CSRF-Token';
    this.cookieName = 'csrf_cookie';
  }
  
  // 生成CSRF Token
  generateToken() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
  }
  
  // 设置CSRF Token
  setToken() {
    const token = this.generateToken();
    
    // 存储到sessionStorage
    sessionStorage.setItem(this.tokenName, token);
    
    // 设置到meta标签
    let metaTag = document.querySelector(`meta[name="${this.tokenName}"]`);
    if (!metaTag) {
      metaTag = document.createElement('meta');
      metaTag.name = this.tokenName;
      document.head.appendChild(metaTag);
    }
    metaTag.content = token;
    
    // 设置到cookie(HttpOnly由服务端设置)
    document.cookie = `${this.cookieName}=${token}; path=/; secure; samesite=strict`;
    
    return token;
  }
  
  // 获取CSRF Token
  getToken() {
    // 优先从sessionStorage获取
    let token = sessionStorage.getItem(this.tokenName);
    
    if (!token) {
      // 从meta标签获取
      const metaTag = document.querySelector(`meta[name="${this.tokenName}"]`);
      token = metaTag ? metaTag.content : null;
    }
    
    if (!token) {
      // 生成新token
      token = this.setToken();
    }
    
    return token;
  }
  
  // 验证CSRF Token
  validateToken(token) {
    const storedToken = this.getToken();
    return token && storedToken && token === storedToken;
  }
  
  // 为表单添加CSRF Token
  addTokenToForm(form) {
    const token = this.getToken();
    
    let tokenInput = form.querySelector(`input[name="${this.tokenName}"]`);
    if (!tokenInput) {
      tokenInput = document.createElement('input');
      tokenInput.type = 'hidden';
      tokenInput.name = this.tokenName;
      form.appendChild(tokenInput);
    }
    
    tokenInput.value = token;
  }
  
  // 为AJAX请求添加CSRF Token
  addTokenToRequest(config = {}) {
    const token = this.getToken();
    
    // 添加到headers
    config.headers = config.headers || {};
    config.headers[this.headerName] = token;
    
    // 如果是POST请求,也添加到body
    if (config.method === 'POST' && config.body instanceof FormData) {
      config.body.append(this.tokenName, token);
    }
    
    return config;
  }
  
  // 拦截所有fetch请求
  interceptFetch() {
    const originalFetch = window.fetch;
    const self = this;
    
    window.fetch = function(url, options = {}) {
      // 只对同源请求添加CSRF Token
      if (self.isSameOrigin(url)) {
        options = self.addTokenToRequest(options);
      }
      
      return originalFetch.call(this, url, options);
    };
  }
  
  // 拦截所有XMLHttpRequest
  interceptXHR() {
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;
    const self = this;
    
    XMLHttpRequest.prototype.open = function(method, url, ...args) {
      this._method = method;
      this._url = url;
      return originalOpen.call(this, method, url, ...args);
    };
    
    XMLHttpRequest.prototype.send = function(data) {
      if (self.isSameOrigin(this._url)) {
        const token = self.getToken();
        this.setRequestHeader(self.headerName, token);
        
        // 如果是FormData,添加token字段
        if (data instanceof FormData) {
          data.append(self.tokenName, token);
        }
      }
      
      return originalSend.call(this, data);
    };
  }
  
  // 检查是否为同源请求
  isSameOrigin(url) {
    try {
      const urlObj = new URL(url, window.location.origin);
      return urlObj.origin === window.location.origin;
    } catch {
      return true; // 相对URL视为同源
    }
  }
  
  // 初始化CSRF防护
  init() {
    this.setToken();
    this.interceptFetch();
    this.interceptXHR();
    this.addFormListeners();
  }
  
  // 为所有表单添加监听器
  addFormListeners() {
    document.addEventListener('submit', (event) => {
      const form = event.target;
      if (form.tagName === 'FORM') {
        this.addTokenToForm(form);
      }
    });
  }
}

// 全局CSRF防护实例
const csrfProtection = new CSRFProtection();

// 页面加载时初始化
document.addEventListener('DOMContentLoaded', () => {
  csrfProtection.init();
});

// React Hook for CSRF protection
import { useEffect, useState } from 'react';

function useCSRFToken() {
  const [token, setToken] = useState('');
  
  useEffect(() => {
    const csrfToken = csrfProtection.getToken();
    setToken(csrfToken);
  }, []);
  
  const refreshToken = () => {
    const newToken = csrfProtection.setToken();
    setToken(newToken);
  };
  
  return { token, refreshToken };
}

// 使用示例
function SecureForm() {
  const { token } = useCSRFToken();
  
  const handleSubmit = async (event) => {
    event.preventDefault();
    
    const formData = new FormData(event.target);
    formData.append('csrf_token', token);
    
    try {
      const response = await fetch('/api/secure-endpoint', {
        method: 'POST',
        body: formData,
        headers: {
          'X-CSRF-Token': token
        }
      });
      
      if (response.ok) {
        console.log('请求成功');
      }
    } catch (error) {
      console.error('请求失败:', error);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input type="hidden" name="csrf_token" value={token} />
      <input type="text" name="data" required />
      <button type="submit">提交</button>
    </form>
  );
}

内容安全策略(CSP)

CSP配置与实现

// CSP策略管理器
class CSPManager {
  constructor() {
    this.policies = {
      'default-src': ["'self'"],
      'script-src': ["'self'", "'unsafe-inline'"],
      'style-src': ["'self'", "'unsafe-inline'"],
      'img-src': ["'self'", 'data:', 'https:'],
      'font-src': ["'self'", 'https://fonts.gstatic.com'],
      'connect-src': ["'self'"],
      'media-src': ["'self'"],
      'object-src': ["'none'"],
      'child-src': ["'self'"],
      'frame-ancestors': ["'none'"],
      'form-action': ["'self'"],
      'base-uri': ["'self'"],
      'manifest-src': ["'self'"]
    };
    
    this.nonces = new Map();
    this.hashes = new Set();
  }
  
  // 生成nonce
  generateNonce() {
    const array = new Uint8Array(16);
    crypto.getRandomValues(array);
    return btoa(String.fromCharCode.apply(null, array));
  }
  
  // 添加nonce到策略
  addNonce(directive, nonce) {
    if (!this.policies[directive]) {
      this.policies[directive] = [];
    }
    
    const nonceValue = `'nonce-${nonce}'`;
    if (!this.policies[directive].includes(nonceValue)) {
      this.policies[directive].push(nonceValue);
    }
    
    this.nonces.set(directive, nonce);
  }
  
  // 计算脚本哈希
  calculateHash(script, algorithm = 'sha256') {
    return crypto.subtle.digest(algorithm.toUpperCase(), new TextEncoder().encode(script))
      .then(hashBuffer => {
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        const hashBase64 = btoa(String.fromCharCode.apply(null, hashArray));
        return `'${algorithm}-${hashBase64}'`;
      });
  }
  
  // 添加脚本哈希
  async addScriptHash(script) {
    const hash = await this.calculateHash(script);
    this.hashes.add(hash);
    
    if (!this.policies['script-src'].includes(hash)) {
      this.policies['script-src'].push(hash);
    }
    
    return hash;
  }
  
  // 添加样式哈希
  async addStyleHash(style) {
    const hash = await this.calculateHash(style);
    this.hashes.add(hash);
    
    if (!this.policies['style-src'].includes(hash)) {
      this.policies['style-src'].push(hash);
    }
    
    return hash;
  }
  
  // 添加域名到策略
  addDomain(directive, domain) {
    if (!this.policies[directive]) {
      this.policies[directive] = [];
    }
    
    if (!this.policies[directive].includes(domain)) {
      this.policies[directive].push(domain);
    }
  }
  
  // 生成CSP字符串
  generateCSP() {
    const cspParts = [];
    
    for (const [directive, sources] of Object.entries(this.policies)) {
      if (sources.length > 0) {
        cspParts.push(`${directive} ${sources.join(' ')}`);
      }
    }
    
    return cspParts.join('; ');
  }
  
  // 设置CSP头部
  setCSPHeader() {
    const csp = this.generateCSP();
    
    // 通过meta标签设置(仅限于某些指令)
    let metaTag = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
    if (!metaTag) {
      metaTag = document.createElement('meta');
      metaTag.setAttribute('http-equiv', 'Content-Security-Policy');
      document.head.appendChild(metaTag);
    }
    metaTag.content = csp;
    
    return csp;
  }
  
  // 处理CSP违规报告
  handleViolation(event) {
    const violation = {
      documentURI: event.documentURI,
      referrer: event.referrer,
      blockedURI: event.blockedURI,
      violatedDirective: event.violatedDirective,
      originalPolicy: event.originalPolicy,
      sourceFile: event.sourceFile,
      lineNumber: event.lineNumber,
      columnNumber: event.columnNumber,
      timestamp: Date.now()
    };
    
    // 发送违规报告到服务器
    this.reportViolation(violation);
    
    // 本地日志记录
    console.warn('CSP Violation:', violation);
  }
  
  // 发送违规报告
  async reportViolation(violation) {
    try {
      await fetch('/api/csp-report', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(violation)
      });
    } catch (error) {
      console.error('Failed to report CSP violation:', error);
    }
  }
  
  // 初始化CSP监听
  init() {
    // 监听CSP违规事件
    document.addEventListener('securitypolicyviolation', (event) => {
      this.handleViolation(event);
    });
    
    // 设置报告端点
    this.policies['report-uri'] = ['/api/csp-report'];
    this.policies['report-to'] = ['csp-endpoint'];
    
    // 生成并设置CSP
    this.setCSPHeader();
  }
  
  // 安全加载外部脚本
  async loadExternalScript(src, options = {}) {
    const nonce = this.generateNonce();
    this.addNonce('script-src', nonce);
    
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.nonce = nonce;
      
      if (options.integrity) {
        script.integrity = options.integrity;
        script.crossOrigin = 'anonymous';
      }
      
      script.onload = () => resolve(script);
      script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
      
      document.head.appendChild(script);
    });
  }
  
  // 安全执行内联脚本
  async executeInlineScript(scriptContent) {
    const hash = await this.addScriptHash(scriptContent);
    
    const script = document.createElement('script');
    script.textContent = scriptContent;
    
    document.head.appendChild(script);
    
    return hash;
  }
}

// 全局CSP管理器
const cspManager = new CSPManager();

// 页面加载时初始化
document.addEventListener('DOMContentLoaded', () => {
  cspManager.init();
});

// 生产环境CSP配置示例
const productionCSP = {
  'default-src': ["'self'"],
  'script-src': [
    "'self'",
    'https://cdn.jsdelivr.net',
    'https://unpkg.com',
    "'sha256-xyz123...'", // 内联脚本哈希
  ],
  'style-src': [
    "'self'",
    'https://fonts.googleapis.com',
    "'unsafe-inline'" // 仅在必要时使用
  ],
  'img-src': [
    "'self'",
    'data:',
    'https:',
    'https://images.unsplash.com'
  ],
  'font-src': [
    "'self'",
    'https://fonts.gstatic.com'
  ],
  'connect-src': [
    "'self'",
    'https://api.example.com',
    'wss://websocket.example.com'
  ],
  'media-src': ["'self'"],
  'object-src': ["'none'"],
  'child-src': ["'none'"],
  'frame-ancestors': ["'none'"],
  'form-action': ["'self'"],
  'base-uri': ["'self'"],
  'upgrade-insecure-requests': [],
  'block-all-mixed-content': []
};

安全开发最佳实践

综合安全防护

// 综合安全防护类
class SecurityManager {
  constructor() {
    this.xssProtection = new XSSProtection();
    this.csrfProtection = new CSRFProtection();
    this.cspManager = new CSPManager();
    this.inputValidator = new InputValidator();
    
    this.securityHeaders = {
      'X-Content-Type-Options': 'nosniff',
      'X-Frame-Options': 'DENY',
      'X-XSS-Protection': '1; mode=block',
      'Referrer-Policy': 'strict-origin-when-cross-origin',
      'Permissions-Policy': 'geolocation=(), microphone=(), camera=()'
    };
  }
  
  // 初始化所有安全防护
  init() {
    this.csrfProtection.init();
    this.cspManager.init();
    this.setupSecureDefaults();
    this.monitorSecurity();
  }
  
  // 设置安全默认值
  setupSecureDefaults() {
    // 禁用右键菜单(可选)
    if (this.shouldDisableContextMenu()) {
      document.addEventListener('contextmenu', (e) => e.preventDefault());
    }
    
    // 禁用开发者工具(生产环境)
    if (process.env.NODE_ENV === 'production') {
      this.disableDevTools();
    }
    
    // 设置安全的默认表单属性
    this.setupSecureForms();
  }
  
  // 禁用开发者工具
  disableDevTools() {
    // 检测开发者工具
    let devtools = { open: false, orientation: null };
    
    setInterval(() => {
      if (window.outerHeight - window.innerHeight > 200 || 
          window.outerWidth - window.innerWidth > 200) {
        if (!devtools.open) {
          devtools.open = true;
          console.warn('开发者工具已被检测到');
          // 可以选择重定向或显示警告
          // window.location.href = '/security-warning';
        }
      } else {
        devtools.open = false;
      }
    }, 500);
  }
  
  // 设置安全表单
  setupSecureForms() {
    document.addEventListener('DOMContentLoaded', () => {
      const forms = document.querySelectorAll('form');
      
      forms.forEach(form => {
        // 添加CSRF保护
        this.csrfProtection.addTokenToForm(form);
        
        // 设置安全属性
        if (!form.hasAttribute('novalidate')) {
          form.setAttribute('novalidate', 'true'); // 使用自定义验证
        }
        
        // 添加输入验证
        const inputs = form.querySelectorAll('input, textarea');
        inputs.forEach(input => {
          this.addInputValidation(input);
        });
      });
    });
  }
  
  // 添加输入验证
  addInputValidation(input) {
    input.addEventListener('input', (event) => {
      const value = event.target.value;
      const type = event.target.type || 'text';
      
      // 实时清理输入
      const sanitized = this.inputValidator.sanitizeInput(value, type);
      if (sanitized !== value) {
        event.target.value = sanitized;
      }
    });
    
    input.addEventListener('blur', (event) => {
      const value = event.target.value;
      const type = event.target.type || 'text';
      
      // 验证输入
      let isValid = true;
      let message = '';
      
      switch (type) {
        case 'email':
          isValid = this.inputValidator.validateEmail(value);
          message = '请输入有效的邮箱地址';
          break;
        case 'password':
          const result = this.inputValidator.validatePassword(value);
          isValid = result.valid;
          message = result.message;
          break;
        default:
          isValid = value.length > 0;
          message = '此字段不能为空';
      }
      
      // 显示验证结果
      this.showValidationResult(input, isValid, message);
    });
  }
  
  // 显示验证结果
  showValidationResult(input, isValid, message) {
    // 移除之前的错误信息
    const existingError = input.parentNode.querySelector('.error-message');
    if (existingError) {
      existingError.remove();
    }
    
    if (!isValid) {
      const errorDiv = document.createElement('div');
      errorDiv.className = 'error-message';
      errorDiv.textContent = message;
      errorDiv.style.color = 'red';
      errorDiv.style.fontSize = '12px';
      
      input.parentNode.appendChild(errorDiv);
      input.style.borderColor = 'red';
    } else {
      input.style.borderColor = '';
    }
  }
  
  // 安全监控
  monitorSecurity() {
    // 监控异常活动
    this.monitorAbnormalActivity();
    
    // 监控网络请求
    this.monitorNetworkRequests();
    
    // 定期安全检查
    setInterval(() => {
      this.performSecurityCheck();
    }, 60000); // 每分钟检查一次
  }
  
  // 监控异常活动
  monitorAbnormalActivity() {
    let clickCount = 0;
    let keyCount = 0;
    
    document.addEventListener('click', () => {
      clickCount++;
      if (clickCount > 100) { // 1分钟内超过100次点击
        console.warn('检测到异常点击活动');
        clickCount = 0;
      }
    });
    
    document.addEventListener('keydown', () => {
      keyCount++;
      if (keyCount > 500) { // 1分钟内超过500次按键
        console.warn('检测到异常键盘活动');
        keyCount = 0;
      }
    });
    
    // 重置计数器
    setInterval(() => {
      clickCount = 0;
      keyCount = 0;
    }, 60000);
  }
  
  // 监控网络请求
  monitorNetworkRequests() {
    const originalFetch = window.fetch;
    
    window.fetch = async function(url, options = {}) {
      // 记录请求
      console.log('Network request:', url, options);
      
      // 检查可疑请求
      if (typeof url === 'string' && url.includes('eval(')) {
        console.error('检测到可疑请求:', url);
        throw new Error('Suspicious request blocked');
      }
      
      return originalFetch.call(this, url, options);
    };
  }
  
  // 执行安全检查
  performSecurityCheck() {
    // 检查DOM是否被篡改
    this.checkDOMIntegrity();
    
    // 检查全局变量
    this.checkGlobalVariables();
    
    // 检查CSP违规
    this.checkCSPViolations();
  }
  
  // 检查DOM完整性
  checkDOMIntegrity() {
    const scripts = document.querySelectorAll('script');
    scripts.forEach(script => {
      if (script.src && !script.src.startsWith(window.location.origin)) {
        console.warn('检测到外部脚本:', script.src);
      }
    });
  }
  
  // 检查全局变量
  checkGlobalVariables() {
    const dangerousFunctions = ['eval', 'Function', 'setTimeout', 'setInterval'];
    
    dangerousFunctions.forEach(funcName => {
      if (window[funcName] && window[funcName].toString().includes('native code')) {
        // 原生函数,正常
      } else {
        console.warn(`全局函数 ${funcName} 可能被篡改`);
      }
    });
  }
  
  // 检查CSP违规
  checkCSPViolations() {
    // 这里可以检查是否有CSP违规记录
    // 实际实现需要与服务端配合
  }
  
  // 判断是否应该禁用右键菜单
  shouldDisableContextMenu() {
    // 根据业务需求决定
    return process.env.NODE_ENV === 'production' && 
           !window.location.hostname.includes('localhost');
  }
}

// 全局安全管理器
const securityManager = new SecurityManager();

// 页面加载时初始化安全防护
document.addEventListener('DOMContentLoaded', () => {
  securityManager.init();
});

// 导出安全工具供其他模块使用
window.Security = {
  xss: securityManager.xssProtection,
  csrf: securityManager.csrfProtection,
  csp: securityManager.cspManager,
  validator: securityManager.inputValidator
};

总结

前端安全防护的核心要点:

  1. XSS防护:输入验证、输出编码、内容过滤
  2. CSRF防护:Token验证、同源检查、安全头设置
  3. CSP策略:内容安全策略配置、违规监控
  4. 输入验证:客户端和服务端双重验证
  5. 安全监控:异常活动检测、安全事件记录

前端安全是一个系统工程,需要从多个维度进行防护,并且要与后端安全措施相配合,形成完整的安全防护体系。