Skip to content

前端安全最佳实践

前端安全是Web应用安全的重要组成部分。随着前端应用复杂度的增加,安全威胁也日益多样化。本文将深入探讨前端安全的各个方面,提供实用的防护策略和最佳实践。

🎯 常见前端安全威胁

1. 跨站脚本攻击 (XSS)

XSS是最常见的前端安全威胁之一,攻击者通过注入恶意脚本来窃取用户信息或执行恶意操作。

反射型XSS

javascript
// 危险示例:直接使用用户输入
function displayWelcome(name) {
  document.getElementById('welcome').innerHTML = `欢迎 ${name}!`;
}

// 安全做法:使用textContent或进行转义
function displayWelcomeSafe(name) {
  const element = document.getElementById('welcome');
  element.textContent = `欢迎 ${name}!`;
  
  // 或者使用转义函数
  element.innerHTML = `欢迎 ${escapeHtml(name)}!`;
}

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

存储型XSS

javascript
// 危险:直接渲染用户生成内容
function renderComment(comment) {
  return `
    <div class="comment">
      <h4>${comment.author}</h4>
      <p>${comment.content}</p>
    </div>
  `;
}

// 安全:使用DOMPurify清理HTML
import DOMPurify from 'dompurify';

function renderCommentSafe(comment) {
  const cleanAuthor = DOMPurify.sanitize(comment.author);
  const cleanContent = DOMPurify.sanitize(comment.content);
  
  return `
    <div class="comment">
      <h4>${cleanAuthor}</h4>
      <p>${cleanContent}</p>
    </div>
  `;
}

DOM型XSS

javascript
// 危险:直接使用URL参数
const urlParams = new URLSearchParams(window.location.search);
const userInput = urlParams.get('q');
document.getElementById('search-result').innerHTML = `搜索结果:${userInput}`;

// 安全:验证和转义输入
function handleSearchQuery() {
  const urlParams = new URLSearchParams(window.location.search);
  const userInput = urlParams.get('q');
  
  if (!userInput || typeof userInput !== 'string') {
    return;
  }
  
  // 限制长度和字符
  const sanitizedInput = userInput
    .slice(0, 100)
    .replace(/[<>'"&]/g, (match) => {
      const escapeMap = {
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#x27;',
        '&': '&amp;'
      };
      return escapeMap[match];
    });
  
  document.getElementById('search-result').textContent = `搜索结果:${sanitizedInput}`;
}

2. 跨站请求伪造 (CSRF)

CSRF攻击利用用户的登录状态执行未授权的操作。

javascript
// CSRF防护:使用CSRF Token
class CSRFProtection {
  constructor() {
    this.token = this.getCSRFToken();
  }
  
  getCSRFToken() {
    // 从meta标签或cookie中获取token
    const metaToken = document.querySelector('meta[name="csrf-token"]');
    return metaToken ? metaToken.getAttribute('content') : null;
  }
  
  async makeSecureRequest(url, options = {}) {
    const headers = {
      'Content-Type': 'application/json',
      'X-CSRF-Token': this.token,
      ...options.headers
    };
    
    return fetch(url, {
      ...options,
      headers,
      credentials: 'same-origin' // 确保cookie被发送
    });
  }
}

// 使用示例
const csrf = new CSRFProtection();

async function updateProfile(profileData) {
  try {
    const response = await csrf.makeSecureRequest('/api/profile', {
      method: 'PUT',
      body: JSON.stringify(profileData)
    });
    
    if (!response.ok) {
      throw new Error('更新失败');
    }
    
    return await response.json();
  } catch (error) {
    console.error('Profile update failed:', error);
    throw error;
  }
}

3. 点击劫持 (Clickjacking)

html
<!-- 防护:使用X-Frame-Options或CSP -->
<meta http-equiv="X-Frame-Options" content="DENY">

<!-- 或者使用CSP -->
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'none';">
javascript
// JavaScript防护
function preventClickjacking() {
  if (window.top !== window.self) {
    // 检测是否在iframe中
    window.top.location = window.self.location;
  }
}

// 更优雅的方式
function frameBreaker() {
  try {
    if (window.top !== window.self) {
      window.top.location.href = window.self.location.href;
    }
  } catch (e) {
    // 如果跨域,会抛出异常
    document.body.style.display = 'none';
    window.top.location = window.self.location;
  }
}

frameBreaker();

🛡️ 内容安全策略 (CSP)

CSP是防止XSS攻击的强大工具,通过限制资源加载来源来提高安全性。

基础CSP配置

html
<!-- 严格的CSP策略 -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; 
               style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; 
               img-src 'self' data: https:; 
               font-src 'self' https://fonts.gstatic.com; 
               connect-src 'self' https://api.example.com;">

动态CSP管理

javascript
class CSPManager {
  constructor() {
    this.nonces = new Map();
  }
  
  generateNonce() {
    const array = new Uint8Array(16);
    crypto.getRandomValues(array);
    return btoa(String.fromCharCode(...array));
  }
  
  addInlineScript(scriptContent, elementId) {
    const nonce = this.generateNonce();
    this.nonces.set(elementId, nonce);
    
    const script = document.createElement('script');
    script.nonce = nonce;
    script.textContent = scriptContent;
    script.id = elementId;
    
    document.head.appendChild(script);
    return nonce;
  }
  
  updateCSP(additionalSources = {}) {
    const currentCSP = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
    if (!currentCSP) return;
    
    let cspContent = currentCSP.getAttribute('content');
    
    // 添加新的源
    Object.entries(additionalSources).forEach(([directive, sources]) => {
      const regex = new RegExp(`${directive}\\s+([^;]+)`);
      const match = cspContent.match(regex);
      
      if (match) {
        const existingSources = match[1];
        const newSources = Array.isArray(sources) ? sources.join(' ') : sources;
        cspContent = cspContent.replace(regex, `${directive} ${existingSources} ${newSources}`);
      }
    });
    
    currentCSP.setAttribute('content', cspContent);
  }
}

// 使用示例
const cspManager = new CSPManager();

// 安全地添加内联脚本
cspManager.addInlineScript(`
  console.log('This is a secure inline script');
`, 'secure-script-1');

// 动态更新CSP
cspManager.updateCSP({
  'script-src': 'https://new-trusted-domain.com',
  'img-src': 'https://images.example.com'
});

🔐 身份验证与授权

JWT安全处理

javascript
class SecureJWTHandler {
  constructor() {
    this.tokenKey = 'auth_token';
    this.refreshKey = 'refresh_token';
  }
  
  // 安全存储token
  storeTokens(accessToken, refreshToken) {
    // 使用httpOnly cookie存储refresh token(更安全)
    // 这需要服务端配合
    
    // 在内存中存储access token
    this.accessToken = accessToken;
    
    // 或者使用sessionStorage(页面关闭后清除)
    sessionStorage.setItem(this.tokenKey, accessToken);
    
    // 设置自动刷新
    this.scheduleTokenRefresh(accessToken);
  }
  
  getAccessToken() {
    return this.accessToken || sessionStorage.getItem(this.tokenKey);
  }
  
  // 解析JWT payload(不验证签名,仅用于获取信息)
  parseJWT(token) {
    try {
      const base64Url = token.split('.')[1];
      const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
      const jsonPayload = decodeURIComponent(
        atob(base64)
          .split('')
          .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
          .join('')
      );
      return JSON.parse(jsonPayload);
    } catch (error) {
      console.error('Invalid JWT token:', error);
      return null;
    }
  }
  
  // 检查token是否即将过期
  isTokenExpiringSoon(token, thresholdMinutes = 5) {
    const payload = this.parseJWT(token);
    if (!payload || !payload.exp) return true;
    
    const expirationTime = payload.exp * 1000;
    const currentTime = Date.now();
    const threshold = thresholdMinutes * 60 * 1000;
    
    return (expirationTime - currentTime) < threshold;
  }
  
  // 自动刷新token
  scheduleTokenRefresh(token) {
    const payload = this.parseJWT(token);
    if (!payload || !payload.exp) return;
    
    const expirationTime = payload.exp * 1000;
    const currentTime = Date.now();
    const refreshTime = expirationTime - currentTime - (5 * 60 * 1000); // 提前5分钟刷新
    
    if (refreshTime > 0) {
      setTimeout(() => {
        this.refreshToken();
      }, refreshTime);
    }
  }
  
  async refreshToken() {
    try {
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        credentials: 'include', // 发送httpOnly cookie
        headers: {
          'Content-Type': 'application/json'
        }
      });
      
      if (response.ok) {
        const { accessToken } = await response.json();
        this.storeTokens(accessToken);
        return accessToken;
      } else {
        this.logout();
      }
    } catch (error) {
      console.error('Token refresh failed:', error);
      this.logout();
    }
  }
  
  logout() {
    this.accessToken = null;
    sessionStorage.removeItem(this.tokenKey);
    // 清除refresh token cookie需要服务端配合
    window.location.href = '/login';
  }
}

权限控制

javascript
class PermissionManager {
  constructor(userPermissions = []) {
    this.permissions = new Set(userPermissions);
  }
  
  hasPermission(permission) {
    return this.permissions.has(permission);
  }
  
  hasAnyPermission(permissions) {
    return permissions.some(permission => this.permissions.has(permission));
  }
  
  hasAllPermissions(permissions) {
    return permissions.every(permission => this.permissions.has(permission));
  }
  
  // 基于角色的权限检查
  hasRole(role) {
    return this.permissions.has(`role:${role}`);
  }
  
  // 资源级权限检查
  canAccessResource(resource, action) {
    return this.permissions.has(`${resource}:${action}`);
  }
}

// 权限装饰器
function requirePermission(permission) {
  return function(target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args) {
      const permissionManager = this.permissionManager || window.permissionManager;
      
      if (!permissionManager || !permissionManager.hasPermission(permission)) {
        throw new Error(`权限不足:需要 ${permission} 权限`);
      }
      
      return originalMethod.apply(this, args);
    };
    
    return descriptor;
  };
}

// 使用示例
class UserService {
  constructor(permissionManager) {
    this.permissionManager = permissionManager;
  }
  
  @requirePermission('user:read')
  getUsers() {
    return fetch('/api/users');
  }
  
  @requirePermission('user:write')
  createUser(userData) {
    return fetch('/api/users', {
      method: 'POST',
      body: JSON.stringify(userData)
    });
  }
  
  @requirePermission('user:delete')
  deleteUser(userId) {
    return fetch(`/api/users/${userId}`, {
      method: 'DELETE'
    });
  }
}

🔒 数据加密与隐私保护

客户端数据加密

javascript
class ClientEncryption {
  constructor() {
    this.algorithm = 'AES-GCM';
    this.keyLength = 256;
  }
  
  // 生成加密密钥
  async generateKey() {
    return await crypto.subtle.generateKey(
      {
        name: this.algorithm,
        length: this.keyLength
      },
      true, // 可导出
      ['encrypt', 'decrypt']
    );
  }
  
  // 加密数据
  async encrypt(data, key) {
    const encoder = new TextEncoder();
    const dataBuffer = encoder.encode(JSON.stringify(data));
    
    const iv = crypto.getRandomValues(new Uint8Array(12)); // 96位IV
    
    const encryptedData = await crypto.subtle.encrypt(
      {
        name: this.algorithm,
        iv: iv
      },
      key,
      dataBuffer
    );
    
    // 返回IV和加密数据
    return {
      iv: Array.from(iv),
      data: Array.from(new Uint8Array(encryptedData))
    };
  }
  
  // 解密数据
  async decrypt(encryptedObj, key) {
    const iv = new Uint8Array(encryptedObj.iv);
    const data = new Uint8Array(encryptedObj.data);
    
    const decryptedData = await crypto.subtle.decrypt(
      {
        name: this.algorithm,
        iv: iv
      },
      key,
      data
    );
    
    const decoder = new TextDecoder();
    const jsonString = decoder.decode(decryptedData);
    return JSON.parse(jsonString);
  }
  
  // 导出密钥(用于存储)
  async exportKey(key) {
    const exported = await crypto.subtle.exportKey('jwk', key);
    return JSON.stringify(exported);
  }
  
  // 导入密钥
  async importKey(keyData) {
    const keyObj = JSON.parse(keyData);
    return await crypto.subtle.importKey(
      'jwk',
      keyObj,
      {
        name: this.algorithm,
        length: this.keyLength
      },
      true,
      ['encrypt', 'decrypt']
    );
  }
}

// 使用示例
const encryption = new ClientEncryption();

async function secureDataStorage() {
  // 生成密钥
  const key = await encryption.generateKey();
  
  // 敏感数据
  const sensitiveData = {
    creditCard: '1234-5678-9012-3456',
    ssn: '123-45-6789',
    personalInfo: 'sensitive information'
  };
  
  // 加密
  const encrypted = await encryption.encrypt(sensitiveData, key);
  
  // 存储加密数据(密钥需要安全存储)
  localStorage.setItem('encryptedData', JSON.stringify(encrypted));
  
  // 解密
  const decrypted = await encryption.decrypt(encrypted, key);
  console.log('Decrypted data:', decrypted);
}

敏感信息处理

javascript
class SensitiveDataHandler {
  constructor() {
    this.sensitiveFields = new Set([
      'password', 'creditCard', 'ssn', 'token', 'secret'
    ]);
  }
  
  // 清理敏感数据
  sanitizeObject(obj, depth = 0) {
    if (depth > 10) return '[Max Depth Reached]'; // 防止循环引用
    
    if (typeof obj !== 'object' || obj === null) {
      return obj;
    }
    
    if (Array.isArray(obj)) {
      return obj.map(item => this.sanitizeObject(item, depth + 1));
    }
    
    const sanitized = {};
    for (const [key, value] of Object.entries(obj)) {
      if (this.isSensitiveField(key)) {
        sanitized[key] = this.maskValue(value);
      } else if (typeof value === 'object') {
        sanitized[key] = this.sanitizeObject(value, depth + 1);
      } else {
        sanitized[key] = value;
      }
    }
    
    return sanitized;
  }
  
  isSensitiveField(fieldName) {
    const lowerField = fieldName.toLowerCase();
    return this.sensitiveFields.has(lowerField) ||
           lowerField.includes('password') ||
           lowerField.includes('token') ||
           lowerField.includes('secret');
  }
  
  maskValue(value) {
    if (typeof value === 'string') {
      if (value.length <= 4) return '***';
      return value.substring(0, 2) + '*'.repeat(value.length - 4) + value.substring(value.length - 2);
    }
    return '[MASKED]';
  }
  
  // 安全的日志记录
  secureLog(level, message, data = {}) {
    const sanitizedData = this.sanitizeObject(data);
    console[level](`[${new Date().toISOString()}] ${message}`, sanitizedData);
  }
  
  // 清理内存中的敏感数据
  clearSensitiveData(obj) {
    if (typeof obj !== 'object' || obj === null) return;
    
    for (const key in obj) {
      if (this.isSensitiveField(key)) {
        if (typeof obj[key] === 'string') {
          // 用随机字符覆盖
          obj[key] = crypto.getRandomValues(new Uint8Array(obj[key].length))
            .reduce((str, byte) => str + String.fromCharCode(byte), '');
        }
        delete obj[key];
      }
    }
  }
}

// 使用示例
const sensitiveHandler = new SensitiveDataHandler();

// 安全日志记录
const userData = {
  username: 'john_doe',
  email: 'john@example.com',
  password: 'secretPassword123',
  creditCard: '1234567890123456'
};

sensitiveHandler.secureLog('info', 'User data processed', userData);
// 输出: User data processed { username: 'john_doe', email: 'john@example.com', password: 'se***********23', creditCard: '12***********56' }

🌐 安全的网络通信

HTTPS和证书验证

javascript
class SecureHTTPClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      'X-Requested-With': 'XMLHttpRequest'
    };
  }
  
  async request(endpoint, options = {}) {
    const url = new URL(endpoint, this.baseURL);
    
    // 确保使用HTTPS
    if (url.protocol !== 'https:' && location.protocol === 'https:') {
      throw new Error('Mixed content: HTTPS页面不能请求HTTP资源');
    }
    
    const config = {
      method: 'GET',
      headers: { ...this.defaultHeaders, ...options.headers },
      credentials: 'same-origin',
      ...options
    };
    
    // 添加完整性检查
    if (options.expectedHash) {
      config.integrity = options.expectedHash;
    }
    
    try {
      const response = await fetch(url.toString(), config);
      
      // 检查响应头
      this.validateSecurityHeaders(response);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      return response;
    } catch (error) {
      console.error('Secure request failed:', error);
      throw error;
    }
  }
  
  validateSecurityHeaders(response) {
    const headers = response.headers;
    
    // 检查HSTS
    if (!headers.get('strict-transport-security')) {
      console.warn('Missing HSTS header');
    }
    
    // 检查CSP
    if (!headers.get('content-security-policy')) {
      console.warn('Missing CSP header');
    }
    
    // 检查X-Frame-Options
    if (!headers.get('x-frame-options')) {
      console.warn('Missing X-Frame-Options header');
    }
  }
  
  // 安全的文件上传
  async uploadFile(file, endpoint, options = {}) {
    // 文件类型验证
    const allowedTypes = options.allowedTypes || ['image/jpeg', 'image/png', 'application/pdf'];
    if (!allowedTypes.includes(file.type)) {
      throw new Error(`不允许的文件类型: ${file.type}`);
    }
    
    // 文件大小验证
    const maxSize = options.maxSize || 10 * 1024 * 1024; // 10MB
    if (file.size > maxSize) {
      throw new Error(`文件过大: ${file.size} bytes`);
    }
    
    // 文件名安全处理
    const safeName = file.name.replace(/[^a-zA-Z0-9.-]/g, '_');
    
    const formData = new FormData();
    formData.append('file', file, safeName);
    
    return this.request(endpoint, {
      method: 'POST',
      body: formData,
      headers: {
        // 不设置Content-Type,让浏览器自动设置boundary
        ...options.headers
      }
    });
  }
}

子资源完整性 (SRI)

javascript
class SRIManager {
  constructor() {
    this.hashCache = new Map();
  }
  
  // 生成资源哈希
  async generateHash(url) {
    try {
      const response = await fetch(url);
      const buffer = await response.arrayBuffer();
      const hashBuffer = await crypto.subtle.digest('SHA-384', buffer);
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      const hashBase64 = btoa(String.fromCharCode(...hashArray));
      return `sha384-${hashBase64}`;
    } catch (error) {
      console.error('Failed to generate hash for:', url, error);
      return null;
    }
  }
  
  // 动态加载带SRI的脚本
  async loadScript(src, options = {}) {
    return new Promise(async (resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.async = options.async !== false;
      script.defer = options.defer || false;
      
      // 生成或使用缓存的哈希
      let integrity = this.hashCache.get(src);
      if (!integrity && options.generateHash) {
        integrity = await this.generateHash(src);
        if (integrity) {
          this.hashCache.set(src, integrity);
        }
      }
      
      if (integrity) {
        script.integrity = integrity;
        script.crossOrigin = 'anonymous';
      }
      
      script.onload = () => resolve(script);
      script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
      
      document.head.appendChild(script);
    });
  }
  
  // 验证现有资源的完整性
  async verifyResourceIntegrity(element) {
    if (!element.integrity || !element.src) {
      return false;
    }
    
    try {
      const actualHash = await this.generateHash(element.src);
      return actualHash === element.integrity;
    } catch (error) {
      console.error('Integrity verification failed:', error);
      return false;
    }
  }
  
  // 批量验证页面资源
  async verifyPageResources() {
    const scripts = document.querySelectorAll('script[src][integrity]');
    const links = document.querySelectorAll('link[href][integrity]');
    
    const results = [];
    
    for (const element of [...scripts, ...links]) {
      const isValid = await this.verifyResourceIntegrity(element);
      results.push({
        element: element.tagName.toLowerCase(),
        src: element.src || element.href,
        valid: isValid
      });
    }
    
    return results;
  }
}

// 使用示例
const sriManager = new SRIManager();

// 安全加载第三方脚本
async function loadTrustedLibrary() {
  try {
    await sriManager.loadScript('https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js', {
      generateHash: true,
      async: true
    });
    console.log('Library loaded securely');
  } catch (error) {
    console.error('Failed to load library:', error);
  }
}

📊 安全监控与日志

安全事件监控

javascript
class SecurityMonitor {
  constructor() {
    this.events = [];
    this.alertThresholds = {
      failedLogins: 5,
      suspiciousRequests: 10,
      xssAttempts: 3
    };
    this.setupEventListeners();
  }
  
  setupEventListeners() {
    // 监控可疑的DOM操作
    this.observeDOM();
    
    // 监控网络请求
    this.interceptFetch();
    
    // 监控控制台访问
    this.monitorConsole();
    
    // 监控页面可见性变化
    document.addEventListener('visibilitychange', () => {
      this.logEvent('visibility_change', {
        hidden: document.hidden,
        timestamp: Date.now()
      });
    });
  }
  
  observeDOM() {
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList') {
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              this.checkSuspiciousElement(node);
            }
          });
        }
      });
    });
    
    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  }
  
  checkSuspiciousElement(element) {
    // 检查可疑的脚本注入
    if (element.tagName === 'SCRIPT' && !element.nonce) {
      this.logSecurityEvent('suspicious_script', {
        src: element.src,
        content: element.textContent.substring(0, 100),
        location: window.location.href
      });
    }
    
    // 检查可疑的iframe
    if (element.tagName === 'IFRAME') {
      const src = element.src;
      if (src && !this.isTrustedDomain(src)) {
        this.logSecurityEvent('suspicious_iframe', {
          src: src,
          location: window.location.href
        });
      }
    }
  }
  
  interceptFetch() {
    const originalFetch = window.fetch;
    
    window.fetch = async (...args) => {
      const [url, options] = args;
      const startTime = Date.now();
      
      try {
        const response = await originalFetch(...args);
        const duration = Date.now() - startTime;
        
        // 记录请求信息
        this.logEvent('network_request', {
          url: url.toString(),
          method: options?.method || 'GET',
          status: response.status,
          duration: duration
        });
        
        // 检查可疑请求
        if (response.status >= 400) {
          this.logSecurityEvent('failed_request', {
            url: url.toString(),
            status: response.status,
            method: options?.method || 'GET'
          });
        }
        
        return response;
      } catch (error) {
        this.logSecurityEvent('network_error', {
          url: url.toString(),
          error: error.message
        });
        throw error;
      }
    };
  }
  
  monitorConsole() {
    const originalLog = console.log;
    const originalError = console.error;
    
    console.log = (...args) => {
      this.checkConsoleAccess();
      return originalLog.apply(console, args);
    };
    
    console.error = (...args) => {
      this.logSecurityEvent('console_error', {
        message: args.join(' '),
        stack: new Error().stack
      });
      return originalError.apply(console, args);
    };
  }
  
  checkConsoleAccess() {
    // 检测开发者工具是否打开
    const threshold = 160;
    if (window.outerHeight - window.innerHeight > threshold ||
        window.outerWidth - window.innerWidth > threshold) {
      this.logSecurityEvent('devtools_detected', {
        timestamp: Date.now(),
        userAgent: navigator.userAgent
      });
    }
  }
  
  isTrustedDomain(url) {
    const trustedDomains = [
      'https://cdn.jsdelivr.net',
      'https://unpkg.com',
      'https://cdnjs.cloudflare.com'
    ];
    
    try {
      const urlObj = new URL(url);
      return trustedDomains.some(domain => 
        urlObj.origin === new URL(domain).origin
      );
    } catch {
      return false;
    }
  }
  
  logEvent(type, data) {
    const event = {
      type,
      data,
      timestamp: Date.now(),
      url: window.location.href,
      userAgent: navigator.userAgent
    };
    
    this.events.push(event);
    
    // 限制事件数量
    if (this.events.length > 1000) {
      this.events = this.events.slice(-500);
    }
  }
  
  logSecurityEvent(type, data) {
    this.logEvent(`security_${type}`, data);
    
    // 检查是否需要发送警报
    this.checkAlertThresholds(type);
    
    // 发送到安全监控服务
    this.sendToSecurityService(type, data);
  }
  
  checkAlertThresholds(eventType) {
    const recentEvents = this.events.filter(event => 
      event.type === `security_${eventType}` &&
      Date.now() - event.timestamp < 300000 // 5分钟内
    );
    
    const threshold = this.alertThresholds[eventType];
    if (threshold && recentEvents.length >= threshold) {
      this.triggerSecurityAlert(eventType, recentEvents.length);
    }
  }
  
  triggerSecurityAlert(eventType, count) {
    console.warn(`Security Alert: ${eventType} occurred ${count} times in 5 minutes`);
    
    // 可以在这里添加更多的警报逻辑
    // 例如:显示用户通知、锁定账户等
  }
  
  async sendToSecurityService(type, data) {
    try {
      await fetch('/api/security/events', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          type,
          data,
          timestamp: Date.now(),
          sessionId: this.getSessionId()
        })
      });
    } catch (error) {
      console.error('Failed to send security event:', error);
    }
  }
  
  getSessionId() {
    let sessionId = sessionStorage.getItem('security_session_id');
    if (!sessionId) {
      sessionId = crypto.randomUUID();
      sessionStorage.setItem('security_session_id', sessionId);
    }
    return sessionId;
  }
  
  // 生成安全报告
  generateSecurityReport() {
    const now = Date.now();
    const last24Hours = this.events.filter(event => 
      now - event.timestamp < 24 * 60 * 60 * 1000
    );
    
    const eventTypes = {};
    last24Hours.forEach(event => {
      eventTypes[event.type] = (eventTypes[event.type] || 0) + 1;
    });
    
    return {
      totalEvents: last24Hours.length,
      eventTypes,
      timeRange: '24 hours',
      generatedAt: new Date().toISOString()
    };
  }
}

// 使用示例
const securityMonitor = new SecurityMonitor();

// 定期生成安全报告
setInterval(() => {
  const report = securityMonitor.generateSecurityReport();
  console.log('Security Report:', report);
}, 60 * 60 * 1000); // 每小时生成一次报告

🚀 安全开发流程

安全代码审查清单

markdown
## 前端安全审查清单

### 输入验证
- [ ] 所有用户输入都经过验证和清理
- [ ] 使用白名单而非黑名单进行验证
- [ ] 对文件上传进行类型和大小限制
- [ ] URL参数经过适当验证

### XSS防护
- [ ] 使用textContent而非innerHTML处理用户数据
- [ ] 实施内容安全策略(CSP)
- [ ] 对动态生成的HTML进行转义
- [ ] 使用DOMPurify等库清理HTML

### CSRF防护
- [ ] 实施CSRF令牌验证
- [ ] 使用SameSite cookie属性
- [ ] 验证Referer头部
- [ ] 对状态改变操作使用POST请求

### 身份验证
- [ ] 使用安全的会话管理
- [ ] 实施适当的密码策略
- [ ] 支持多因素认证
- [ ] 安全存储认证令牌

### 数据保护
- [ ] 敏感数据加密存储
- [ ] 使用HTTPS传输
- [ ] 实施数据最小化原则
- [ ] 定期清理敏感数据

### 第三方依赖
- [ ] 定期更新依赖包
- [ ] 使用子资源完整性(SRI)
- [ ] 审查第三方库的安全性
- [ ] 限制第三方脚本权限

自动化安全测试

javascript
class SecurityTester {
  constructor() {
    this.tests = [];
    this.results = [];
  }
  
  // XSS测试
  testXSSVulnerabilities() {
    const xssPayloads = [
      '<script>alert("XSS")</script>',
      'javascript:alert("XSS")',
      '<img src="x" onerror="alert(\'XSS\')">',
      '<svg onload="alert(\'XSS\')">',
      '"><script>alert("XSS")</script>'
    ];
    
    const inputFields = document.querySelectorAll('input, textarea');
    const results = [];
    
    inputFields.forEach((field, index) => {
      xssPayloads.forEach((payload, payloadIndex) => {
        try {
          field.value = payload;
          field.dispatchEvent(new Event('input', { bubbles: true }));
          
          // 检查是否执行了脚本
          const isVulnerable = this.checkForXSSExecution();
          
          results.push({
            field: `Field ${index}`,
            payload: payload,
            vulnerable: isVulnerable,
            test: `XSS Test ${payloadIndex + 1}`
          });
        } catch (error) {
          results.push({
            field: `Field ${index}`,
            payload: payload,
            error: error.message,
            test: `XSS Test ${payloadIndex + 1}`
          });
        }
      });
    });
    
    return results;
  }
  
  checkForXSSExecution() {
    // 简单的XSS检测逻辑
    // 在实际应用中,这需要更复杂的检测机制
    const scripts = document.querySelectorAll('script');
    const lastScript = scripts[scripts.length - 1];
    
    if (lastScript && lastScript.textContent.includes('alert')) {
      return true;
    }
    
    return false;
  }
  
  // CSRF测试
  testCSRFProtection() {
    const forms = document.querySelectorAll('form');
    const results = [];
    
    forms.forEach((form, index) => {
      const hasCSRFToken = form.querySelector('input[name*="csrf"], input[name*="token"]');
      const method = form.method.toLowerCase();
      
      results.push({
        form: `Form ${index}`,
        method: method,
        hasCSRFToken: !!hasCSRFToken,
        vulnerable: method === 'post' && !hasCSRFToken,
        action: form.action
      });
    });
    
    return results;
  }
  
  // 检查安全头部
  async testSecurityHeaders() {
    try {
      const response = await fetch(window.location.href, {
        method: 'HEAD'
      });
      
      const headers = {
        'strict-transport-security': response.headers.get('strict-transport-security'),
        'content-security-policy': response.headers.get('content-security-policy'),
        'x-frame-options': response.headers.get('x-frame-options'),
        'x-content-type-options': response.headers.get('x-content-type-options'),
        'referrer-policy': response.headers.get('referrer-policy')
      };
      
      const results = [];
      
      Object.entries(headers).forEach(([header, value]) => {
        results.push({
          header: header,
          present: !!value,
          value: value || 'Not set',
          secure: this.evaluateHeaderSecurity(header, value)
        });
      });
      
      return results;
    } catch (error) {
      return [{ error: 'Failed to fetch headers', message: error.message }];
    }
  }
  
  evaluateHeaderSecurity(header, value) {
    if (!value) return false;
    
    switch (header) {
      case 'strict-transport-security':
        return value.includes('max-age') && parseInt(value.match(/max-age=(\d+)/)?.[1] || '0') > 31536000;
      case 'content-security-policy':
        return !value.includes('unsafe-inline') && !value.includes('unsafe-eval');
      case 'x-frame-options':
        return value.toLowerCase() === 'deny' || value.toLowerCase() === 'sameorigin';
      case 'x-content-type-options':
        return value.toLowerCase() === 'nosniff';
      case 'referrer-policy':
        return ['strict-origin', 'strict-origin-when-cross-origin', 'no-referrer'].includes(value.toLowerCase());
      default:
        return true;
    }
  }
  
  // 运行所有测试
  async runAllTests() {
    console.log('🔒 Running security tests...');
    
    const results = {
      xss: this.testXSSVulnerabilities(),
      csrf: this.testCSRFProtection(),
      headers: await this.testSecurityHeaders(),
      timestamp: new Date().toISOString()
    };
    
    this.generateSecurityReport(results);
    return results;
  }
  
  generateSecurityReport(results) {
    console.group('🛡️ Security Test Report');
    
    // XSS测试结果
    console.group('XSS Vulnerabilities');
    const xssVulnerable = results.xss.filter(test => test.vulnerable);
    console.log(`Found ${xssVulnerable.length} potential XSS vulnerabilities`);
    xssVulnerable.forEach(vuln => console.warn(vuln));
    console.groupEnd();
    
    // CSRF测试结果
    console.group('CSRF Protection');
    const csrfVulnerable = results.csrf.filter(test => test.vulnerable);
    console.log(`Found ${csrfVulnerable.length} forms without CSRF protection`);
    csrfVulnerable.forEach(vuln => console.warn(vuln));
    console.groupEnd();
    
    // 安全头部测试结果
    console.group('Security Headers');
    const insecureHeaders = results.headers.filter(header => !header.secure);
    console.log(`Found ${insecureHeaders.length} insecure or missing headers`);
    insecureHeaders.forEach(header => console.warn(header));
    console.groupEnd();
    
    console.groupEnd();
  }
}

// 使用示例
const securityTester = new SecurityTester();

// 在开发环境中运行安全测试
if (process.env.NODE_ENV === 'development') {
  // 页面加载完成后运行测试
  window.addEventListener('load', async () => {
    await securityTester.runAllTests();
  });
}

📋 安全最佳实践总结

开发阶段

  1. 输入验证:始终验证和清理用户输入
  2. 输出编码:对输出到HTML的数据进行适当编码
  3. 最小权限原则:只授予必要的权限
  4. 安全默认值:使用安全的默认配置

部署阶段

  1. HTTPS强制:所有通信使用HTTPS
  2. 安全头部:配置适当的HTTP安全头部
  3. 依赖更新:定期更新依赖包
  4. 监控告警:实施安全监控和告警

运维阶段

  1. 日志审计:记录和审计安全相关事件
  2. 漏洞扫描:定期进行安全漏洞扫描
  3. 应急响应:制定安全事件应急响应计划
  4. 安全培训:定期进行安全意识培训

🔗 参考资源

总结

前端安全是一个持续的过程,需要在开发的每个阶段都保持警惕。通过实施本文介绍的安全措施和最佳实践,可以显著提高Web应用的安全性。记住,安全不是一次性的任务,而是需要持续关注和改进的过程。

vitepress开发指南