Skip to content
On this page

qiankun 安全性

安全性概述

qiankun 微前端架构引入了多个应用在同一个页面中运行的复杂性,这带来了新的安全挑战。安全性主要涉及隔离机制、通信安全、资源加载安全等方面。

沙箱安全机制

1. JS 沙箱安全

代理沙箱(Proxy Sandbox)

qiankun 默认使用基于 Proxy 的沙箱机制来隔离微应用的 JavaScript 执行环境:

javascript
// qiankun 内部的沙箱实现原理(简化版)
class ProxySandbox {
  constructor() {
    this.proxy = new Proxy({}, {
      set: (target, prop, value) => {
        // 拦截全局变量设置
        if (this.isGlobalProperty(prop)) {
          this.globalSnapshot[prop] = value;
        } else {
          target[prop] = value;
        }
        return true;
      },
      get: (target, prop) => {
        // 拦截全局变量读取
        if (this.isGlobalProperty(prop)) {
          return this.globalSnapshot[prop] ?? globalContext[prop];
        }
        return target[prop];
      }
    });
  }
  
  isGlobalProperty(prop) {
    // 判断是否为全局属性
    return globalContext.hasOwnProperty(prop);
  }
}

沙箱配置

javascript
// 主应用中配置沙箱安全选项
import { start } from 'qiankun';

start({
  sandbox: {
    // 启用沙箱(默认启用)
    strictStyleIsolation: false, // 严格样式隔离
    experimentalStyleIsolation: true, // 实验性样式隔离
    // 沙箱类型
    type: 'legacy', // 'legacy' | 'proxy' | 'snapshot'
  }
});

2. 样式隔离安全

严格样式隔离

javascript
// 启用严格样式隔离防止 CSS 污染
start({
  sandbox: {
    strictStyleIsolation: true,
  }
});

// qiankun 会为微应用样式添加属性选择器
// 例如:div[data-qiankun="app1"] .component {}

实验性样式隔离

javascript
// 使用实验性样式隔离(更安全)
start({
  sandbox: {
    experimentalStyleIsolation: true,
  }
});

// 这会将微应用包裹在特殊容器中实现更严格的隔离

通信安全

1. 数据传递安全

安全的数据传递

javascript
// 主应用向微应用传递数据时的验证
function validateProps(props) {
  // 验证数据类型
  if (props && typeof props === 'object') {
    // 验证特定字段
    if (props.userData && typeof props.userData !== 'object') {
      throw new Error('Invalid userData format');
    }
    
    if (props.permissions && !Array.isArray(props.permissions)) {
      throw new Error('Permissions must be an array');
    }
    
    // 验证回调函数的安全性
    if (props.onUserAction && typeof props.onUserAction !== 'function') {
      throw new Error('onUserAction must be a function');
    }
  }
  
  return true;
}

// 安全地注册微应用
registerMicroApps([
  {
    name: 'secure-app',
    entry: '//trusted-domain.com/app',
    container: '#container',
    activeRule: '/secure',
    props: {
      userData: sanitizeUserData(getUserData()),
      permissions: getUserPermissions(),
      onUserAction: createSecureCallback()
    }
  }
]);

function sanitizeUserData(userData) {
  // 清理用户数据,移除敏感信息
  return {
    id: userData.id,
    name: userData.name,
    // 不传递敏感信息如密码、token等
  };
}

function createSecureCallback() {
  return function secureAction(data) {
    // 验证回调数据
    if (!isValidCallbackData(data)) {
      console.error('Invalid callback data');
      return;
    }
    
    // 安全地处理回调
    processCallbackData(data);
  };
}

2. 全局状态安全

安全的全局状态管理

javascript
// 主应用初始化安全的全局状态
import { initGlobalState } from 'qiankun';

const { setGlobalState, onGlobalStateChange } = initGlobalState({
  // 只传递必要的安全数据
  userInfo: {
    id: getCurrentUserId(),
    name: getCurrentUserName(),
    // 不包含敏感信息
  },
  theme: getTheme(),
  language: getLanguage(),
});

// 监听全局状态变化并验证
onGlobalStateChange((state, prev) => {
  // 验证状态变更的安全性
  if (!isValidStateChange(state, prev)) {
    console.warn('Potentially unsafe state change detected');
    // 可以选择回滚或忽略不安全的变更
    return;
  }
  
  updateMainAppState(state);
}, true);

function isValidStateChange(newState, prevState) {
  // 验证状态变更的合法性
  if (newState.userInfo && newState.userInfo.token) {
    // 不允许微应用设置认证令牌
    return false;
  }
  
  // 验证其他敏感字段
  const sensitiveFields = ['password', 'token', 'apiKey', 'secret'];
  for (const field of sensitiveFields) {
    if (newState.hasOwnProperty(field)) {
      return false;
    }
  }
  
  return true;
}

资源加载安全

1. 微应用入口安全

验证微应用来源

javascript
// 安全的微应用注册
const ALLOWED_DOMAINS = [
  'https://trusted-apps.company.com',
  'https://microapps.company.com'
];

function isValidMicroAppEntry(entry) {
  try {
    const url = new URL(entry);
    return ALLOWED_DOMAINS.some(domain => 
      url.origin === domain || url.origin.endsWith(domain.replace('https://', '.'))
    );
  } catch (e) {
    return false;
  }
}

// 注册前验证入口
const secureApps = microApps.filter(app => {
  if (!isValidMicroAppEntry(app.entry)) {
    console.error(`Invalid micro app entry: ${app.entry}`);
    return false;
  }
  return true;
});

registerMicroApps(secureApps);

2. 内容安全策略(CSP)

CSP 配置

html
<!-- 主应用的 CSP 配置 -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self' 'unsafe-inline' trusted-cdn.com; 
               style-src 'self' 'unsafe-inline' trusted-styles.com; 
               connect-src 'self' api.trusted.com;
               frame-src trusted-frames.com;">

动态 CSP 管理

javascript
// 动态管理 CSP 策略
class CSPManager {
  constructor() {
    this.allowedSources = {
      scripts: ['self', 'trusted-cdn.com'],
      styles: ['self', 'trusted-styles.com'],
      connect: ['self', 'api.trusted.com']
    };
  }
  
  addMicroAppSources(microAppName, sources) {
    // 为特定微应用添加安全源
    if (sources.scripts) {
      this.allowedSources.scripts.push(...sources.scripts);
    }
    if (sources.connect) {
      this.allowedSources.connect.push(...sources.connect);
    }
  }
  
  generateCSPHeader() {
    return {
      'Content-Security-Policy': 
        `script-src ${this.formatSources(this.allowedSources.scripts)}; ` +
        `connect-src ${this.formatSources(this.allowedSources.connect)}; ` +
        `style-src ${this.formatSources(this.allowedSources.styles)}`
    };
  }
  
  formatSources(sources) {
    return sources.map(src => {
      if (src === 'self') return "'self'";
      if (src === 'unsafe-inline') return "'unsafe-inline'";
      return src;
    }).join(' ');
  }
}

const cspManager = new CSPManager();

通信安全加固

1. 通信数据加密

数据加密传输

javascript
// 微应用通信加密
class SecureCommunication {
  constructor() {
    this.encryptionKey = this.generateKey();
  }
  
  async encrypt(data) {
    // 使用 Web Crypto API 进行加密
    if (window.crypto && window.crypto.subtle) {
      const encodedData = new TextEncoder().encode(JSON.stringify(data));
      const encrypted = await window.crypto.subtle.encrypt(
        { name: 'AES-GCM', iv: window.crypto.getRandomValues(new Uint8Array(12)) },
        this.encryptionKey,
        encodedData
      );
      return encrypted;
    }
    return data; // 降级处理
  }
  
  async decrypt(encryptedData) {
    if (window.crypto && window.crypto.subtle) {
      const decrypted = await window.crypto.subtle.decrypt(
        { name: 'AES-GCM', iv: window.crypto.getRandomValues(new Uint8Array(12)) },
        this.encryptionKey,
        encryptedData
      );
      const decoder = new TextDecoder();
      return JSON.parse(decoder.decode(decrypted));
    }
    return encryptedData;
  }
  
  generateKey() {
    // 生成加密密钥
    return window.crypto.subtle.generateKey(
      { name: 'AES-GCM', length: 256 },
      true,
      ['encrypt', 'decrypt']
    );
  }
}

const secureComm = new SecureCommunication();

2. 通信验证

通信数据验证

javascript
// 通信数据验证机制
class CommunicationValidator {
  constructor() {
    this.messageCounter = new Map(); // 防重放攻击
    this.rateLimits = new Map(); // 限流
  }
  
  async validateMessage(message, source) {
    // 检查消息格式
    if (!this.isValidMessageFormat(message)) {
      return false;
    }
    
    // 检查消息频率(防刷)
    if (!await this.checkRateLimit(source)) {
      return false;
    }
    
    // 检查消息唯一性(防重放)
    if (!this.checkMessageUniqueness(message)) {
      return false;
    }
    
    return true;
  }
  
  isValidMessageFormat(message) {
    // 验证消息格式
    if (typeof message !== 'object' || message === null) {
      return false;
    }
    
    // 检查必要字段
    if (!message.type || !message.payload) {
      return false;
    }
    
    // 验证字段类型
    if (typeof message.type !== 'string') {
      return false;
    }
    
    return true;
  }
  
  async checkRateLimit(source) {
    const now = Date.now();
    const limit = 10; // 每秒最多10条消息
    const timeWindow = 1000; // 1秒窗口
    
    if (!this.rateLimits.has(source)) {
      this.rateLimits.set(source, { count: 0, timestamp: now });
    }
    
    const rateInfo = this.rateLimits.get(source);
    
    if (now - rateInfo.timestamp > timeWindow) {
      // 重置计数器
      rateInfo.count = 1;
      rateInfo.timestamp = now;
    } else {
      rateInfo.count++;
      if (rateInfo.count > limit) {
        console.warn(`Rate limit exceeded for ${source}`);
        return false;
      }
    }
    
    return true;
  }
  
  checkMessageUniqueness(message) {
    const messageId = message.id || this.generateMessageId(message);
    const lastSeen = this.messageCounter.get(messageId);
    const now = Date.now();
    
    // 检查是否为重放消息(5分钟内不允许重复)
    if (lastSeen && now - lastSeen < 5 * 60 * 1000) {
      console.warn('Potential replay attack detected');
      return false;
    }
    
    this.messageCounter.set(messageId, now);
    
    // 清理过期的消息ID
    this.cleanupOldMessages();
    
    return true;
  }
  
  generateMessageId(message) {
    // 生成消息唯一标识
    return btoa(JSON.stringify(message)).substring(0, 16);
  }
  
  cleanupOldMessages() {
    const now = Date.now();
    const timeout = 10 * 60 * 1000; // 10分钟超时
    
    for (const [id, timestamp] of this.messageCounter) {
      if (now - timestamp > timeout) {
        this.messageCounter.delete(id);
      }
    }
  }
}

const validator = new CommunicationValidator();

XSS 防护

1. 输入验证和清理

微应用输入验证

javascript
// 微应用输入验证和清理
class InputSanitizer {
  static sanitizeHTML(input) {
    if (typeof input !== 'string') {
      return '';
    }
    
    // 使用 DOMPurify 或类似库清理 HTML
    if (window.DOMPurify) {
      return DOMPurify.sanitize(input);
    }
    
    // 简单的清理实现
    const div = document.createElement('div');
    div.textContent = input;
    return div.innerHTML;
  }
  
  static validateURL(url) {
    try {
      const parsed = new URL(url);
      // 只允许安全的协议
      return ['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol);
    } catch {
      return false;
    }
  }
  
  static sanitizeUserData(userData) {
    if (!userData || typeof userData !== 'object') {
      return null;
    }
    
    return {
      id: this.sanitizeValue(userData.id, 'number'),
      name: this.sanitizeValue(userData.name, 'string'),
      email: this.sanitizeValue(userData.email, 'email'),
      // 过滤敏感字段
    };
  }
  
  static sanitizeValue(value, type) {
    switch (type) {
      case 'string':
        return typeof value === 'string' ? value.trim() : '';
      case 'number':
        return typeof value === 'number' ? value : 0;
      case 'email':
        return typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) 
          ? value : '';
      default:
        return value;
    }
  }
}

// 在微应用中使用
export async function mount(props) {
  const { userData, settings } = props;
  
  // 验证和清理输入数据
  const sanitizedUserData = InputSanitizer.sanitizeUserData(userData);
  const sanitizedSettings = InputSanitizer.sanitizeValue(settings, 'object');
  
  // 使用清理后的数据
  initializeApp(sanitizedUserData, sanitizedSettings);
}

2. 输出编码

安全输出

javascript
// 安全输出到 DOM
class SecureOutput {
  static insertText(element, text) {
    // 使用 textContent 而不是 innerHTML
    element.textContent = text;
  }
  
  static insertHTML(element, html) {
    // 先清理再插入
    const sanitized = InputSanitizer.sanitizeHTML(html);
    element.innerHTML = sanitized;
  }
  
  static setAttribute(element, name, value) {
    // 验证属性名和值
    if (this.isValidAttributeName(name) && this.isValidAttributeValue(value)) {
      element.setAttribute(name, value);
    }
  }
  
  static isValidAttributeName(name) {
    // 验证属性名(防止事件处理器等危险属性)
    const dangerousAttrs = ['on*', 'srcdoc', 'data'];
    return !dangerousAttrs.some(danger => 
      name.toLowerCase().startsWith(danger.replace('*', ''))
    );
  }
  
  static isValidAttributeValue(value) {
    // 验证属性值
    if (typeof value !== 'string') return true;
    
    const lowerValue = value.toLowerCase();
    const dangerousPatterns = [
      'javascript:',
      'data:',
      'vbscript:',
      'script'
    ];
    
    return !dangerousPatterns.some(pattern => 
      lowerValue.includes(pattern)
    );
  }
}

安全最佳实践

1. 安全配置检查清单

javascript
// qiankun 安全配置检查
const SecurityConfig = {
  // 沙箱配置
  sandbox: {
    enabled: true,
    strictStyleIsolation: true,
    experimentalStyleIsolation: false, // 生产环境谨慎使用
    type: 'legacy' // 代理沙箱
  },
  
  // 预加载配置
  prefetch: {
    enabled: true,
    validateEntries: true // 验证入口地址
  },
  
  // 通信安全
  communication: {
    validateProps: true,
    sanitizeData: true,
    limitMessageRate: true
  },
  
  // 资源安全
  resources: {
    validateCORS: true,
    checkContentSecurity: true
  }
};

// 安全启动函数
function secureStart() {
  // 验证安全配置
  if (!isSecurityConfigValid(SecurityConfig)) {
    throw new Error('Security configuration is not valid');
  }
  
  start({
    sandbox: SecurityConfig.sandbox,
    prefetch: SecurityConfig.prefetch.enabled,
    // 其他安全配置
  });
}

function isSecurityConfigValid(config) {
  // 验证配置的安全性
  return config.sandbox.enabled && 
         config.sandbox.strictStyleIsolation;
}

2. 安全监控

安全事件监控

javascript
// 安全监控工具
class SecurityMonitor {
  constructor() {
    this.securityEvents = [];
    this.alertThresholds = {
      xssAttempts: 5,
      invalidMessages: 10,
      unauthorizedAccess: 2
    };
  }
  
  logSecurityEvent(eventType, details) {
    const event = {
      type: eventType,
      timestamp: Date.now(),
      details,
      source: this.getCurrentContext()
    };
    
    this.securityEvents.push(event);
    
    // 检查是否超过阈值
    if (this.checkThresholds(eventType)) {
      this.triggerSecurityAlert(eventType, details);
    }
    
    // 保留最近1000个事件
    if (this.securityEvents.length > 1000) {
      this.securityEvents = this.securityEvents.slice(-1000);
    }
  }
  
  checkThresholds(eventType) {
    const recentEvents = this.securityEvents.filter(
      e => e.type === eventType && 
           Date.now() - e.timestamp < 60000 // 1分钟内
    );
    
    return recentEvents.length >= (this.alertThresholds[eventType] || 10);
  }
  
  triggerSecurityAlert(eventType, details) {
    console.error(`SECURITY ALERT: ${eventType}`, details);
    
    // 可以发送警报到安全监控系统
    this.sendToSecuritySystem({
      level: 'HIGH',
      eventType,
      details,
      timestamp: new Date().toISOString()
    });
  }
  
  getCurrentContext() {
    return {
      url: window.location.href,
      userAgent: navigator.userAgent,
      timestamp: Date.now()
    };
  }
  
  sendToSecuritySystem(alert) {
    // 发送安全警报到监控系统
    fetch('/api/security-alerts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(alert)
    }).catch(err => {
      console.error('Failed to send security alert:', err);
    });
  }
}

const securityMonitor = new SecurityMonitor();

// 监听安全相关事件
window.addEventListener('securitypolicyviolation', (e) => {
  securityMonitor.logSecurityEvent('CSP_VIOLATION', {
    blockedURI: e.blockedURI,
    violatedDirective: e.violatedDirective,
    effectiveDirective: e.effectiveDirective
  });
});

通过实施这些安全措施,可以显著提高 qiankun 微前端应用的安全性,防止常见的安全威胁。