Appearance
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 微前端应用的安全性,防止常见的安全威胁。