Skip to content

13.2 HTML5安全特性

关键词: HTML5安全特性, 同源策略, 沙箱属性, 安全数据存储, 安全API, 用户输入验证, Web安全, 权限控制

学习目标

  • 理解HTML5中的同源策略机制和应用
  • 掌握iframe沙箱属性的使用方法
  • 学会安全地使用HTML5存储API
  • 了解HTML5 API的安全使用最佳实践
  • 掌握用户输入验证的各种技术

13.2.1 同源策略

同源策略基础

同源策略是Web安全的基石,它限制了一个源的文档或脚本如何与另一个源的资源进行交互。

html
<!-- 同源策略示例 -->
<script>
// 同源检测函数
function isSameOrigin(url1, url2) {
    const a = document.createElement('a');
    const b = document.createElement('a');
    
    a.href = url1;
    b.href = url2;
    
    return a.protocol === b.protocol && 
           a.hostname === b.hostname && 
           a.port === b.port;
}

// 测试同源策略
const currentOrigin = window.location.origin;
console.log('当前源:', currentOrigin);

// 同源示例
console.log('同源检测:', isSameOrigin(
    'https://example.com/page1', 
    'https://example.com/page2'
)); // true

// 不同源示例
console.log('跨源检测:', isSameOrigin(
    'https://example.com/page1', 
    'https://another.com/page2'
)); // false
</script>

跨域资源共享 (CORS)

html
<!-- CORS跨域请求示例 -->
<script>
// 安全的跨域请求处理
class SecureCORSHandler {
    constructor() {
        this.allowedOrigins = [
            'https://trusted-api.com',
            'https://cdn.trusted-site.com'
        ];
    }
    
    // 发送CORS请求
    async makeSecureRequest(url, options = {}) {
        // 验证目标URL是否在允许列表中
        if (!this.isAllowedOrigin(url)) {
            throw new Error('请求的源不在允许列表中');
        }
        
        const defaultOptions = {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                'X-Requested-With': 'XMLHttpRequest'
            },
            credentials: 'same-origin' // 默认不发送跨域凭据
        };
        
        const mergedOptions = { ...defaultOptions, ...options };
        
        try {
            const response = await fetch(url, mergedOptions);
            
            // 检查响应状态
            if (!response.ok) {
                throw new Error(`HTTP错误: ${response.status}`);
            }
            
            // 验证响应头
            this.validateCORSHeaders(response);
            
            return response;
        } catch (error) {
            console.error('CORS请求失败:', error);
            throw error;
        }
    }
    
    // 检查源是否被允许
    isAllowedOrigin(url) {
        const origin = new URL(url).origin;
        return this.allowedOrigins.includes(origin);
    }
    
    // 验证CORS响应头
    validateCORSHeaders(response) {
        const accessControlAllowOrigin = response.headers.get('Access-Control-Allow-Origin');
        const accessControlAllowCredentials = response.headers.get('Access-Control-Allow-Credentials');
        
        if (accessControlAllowOrigin === '*' && accessControlAllowCredentials === 'true') {
            console.warn('不安全的CORS配置:通配符源与凭据组合');
        }
    }
}

// 使用示例
const corsHandler = new SecureCORSHandler();

// 安全的API请求
corsHandler.makeSecureRequest('https://trusted-api.com/data')
    .then(response => response.json())
    .then(data => console.log('数据:', data))
    .catch(error => console.error('请求失败:', error));
</script>

同源策略绕过防护

html
<!-- 防止同源策略绕过 -->
<script>
// 防止window.name攻击
function secureWindowName() {
    // 清理window.name
    if (window.name && window.name.length > 0) {
        console.warn('检测到window.name数据,可能存在安全风险');
        window.name = '';
    }
}

// 防止document.domain攻击
function secureDocumentDomain() {
    // 检查document.domain是否被修改
    const currentDomain = document.domain;
    const locationHostname = window.location.hostname;
    
    if (currentDomain !== locationHostname) {
        console.warn('document.domain被修改,可能存在安全风险');
        // 重置为原始域名
        document.domain = locationHostname;
    }
}

// 防止postMessage攻击
function setupSecurePostMessage() {
    window.addEventListener('message', function(event) {
        // 验证来源
        const trustedOrigins = [
            'https://trusted-site.com',
            'https://another-trusted-site.com'
        ];
        
        if (!trustedOrigins.includes(event.origin)) {
            console.warn('收到来自不可信源的消息:', event.origin);
            return;
        }
        
        // 验证消息内容
        if (typeof event.data !== 'object' || event.data === null) {
            console.warn('收到无效的消息数据');
            return;
        }
        
        // 验证消息类型
        if (!event.data.type || typeof event.data.type !== 'string') {
            console.warn('消息缺少类型字段');
            return;
        }
        
        // 处理消息
        handleSecureMessage(event.data, event.origin);
    });
}

// 安全消息处理
function handleSecureMessage(data, origin) {
    switch (data.type) {
        case 'greeting':
            console.log(`来自${origin}的问候:`, data.message);
            break;
        case 'data-update':
            updateData(data.payload);
            break;
        default:
            console.warn('未知消息类型:', data.type);
    }
}

// 初始化安全措施
secureWindowName();
secureDocumentDomain();
setupSecurePostMessage();
</script>

13.2.2 沙箱属性

iframe沙箱基础

html
<!-- 基本沙箱配置 -->
<div class="sandbox-examples">
    <h3>iframe沙箱示例</h3>
    
    <!-- 完全沙箱 - 最严格的限制 -->
    <iframe src="untrusted-content.html" sandbox></iframe>
    
    <!-- 允许脚本执行 -->
    <iframe src="trusted-content.html" sandbox="allow-scripts"></iframe>
    
    <!-- 允许表单提交 -->
    <iframe src="form-content.html" sandbox="allow-forms"></iframe>
    
    <!-- 允许同源访问 -->
    <iframe src="same-origin-content.html" sandbox="allow-same-origin"></iframe>
    
    <!-- 允许弹出窗口 -->
    <iframe src="popup-content.html" sandbox="allow-popups"></iframe>
    
    <!-- 允许顶层导航 -->
    <iframe src="navigation-content.html" sandbox="allow-top-navigation"></iframe>
    
    <!-- 组合权限 -->
    <iframe src="mixed-content.html" 
            sandbox="allow-scripts allow-forms allow-same-origin">
    </iframe>
</div>

动态沙箱管理

javascript
// 沙箱管理器
class SandboxManager {
    constructor() {
        this.sandboxPolicies = {
            'untrusted': [],
            'limited': ['allow-scripts'],
            'forms': ['allow-forms', 'allow-scripts'],
            'trusted': ['allow-scripts', 'allow-forms', 'allow-same-origin'],
            'full': ['allow-scripts', 'allow-forms', 'allow-same-origin', 'allow-popups']
        };
    }
    
    // 创建沙箱iframe
    createSandboxIframe(src, trustLevel = 'untrusted', container = document.body) {
        const iframe = document.createElement('iframe');
        iframe.src = src;
        
        // 设置沙箱属性
        const policies = this.sandboxPolicies[trustLevel] || [];
        if (policies.length > 0) {
            iframe.sandbox = policies.join(' ');
        } else {
            iframe.sandbox = ''; // 空字符串表示最严格的沙箱
        }
        
        // 设置安全属性
        iframe.style.border = '1px solid #ccc';
        iframe.style.width = '100%';
        iframe.style.height = '400px';
        
        // 添加加载事件监听
        iframe.addEventListener('load', () => {
            this.validateSandboxContent(iframe);
        });
        
        container.appendChild(iframe);
        return iframe;
    }
    
    // 验证沙箱内容
    validateSandboxContent(iframe) {
        try {
            // 尝试访问iframe内容(如果允许同源)
            if (iframe.sandbox.includes('allow-same-origin')) {
                const iframeDoc = iframe.contentDocument;
                if (iframeDoc) {
                    // 扫描潜在的安全问题
                    this.scanForSecurityIssues(iframeDoc);
                }
            }
        } catch (error) {
            // 预期的跨域错误
            console.log('沙箱内容验证完成(跨域限制)');
        }
    }
    
    // 扫描安全问题
    scanForSecurityIssues(doc) {
        // 检查内联脚本
        const inlineScripts = doc.querySelectorAll('script:not([src])');
        if (inlineScripts.length > 0) {
            console.warn('检测到内联脚本,可能存在安全风险');
        }
        
        // 检查外部脚本
        const externalScripts = doc.querySelectorAll('script[src]');
        externalScripts.forEach(script => {
            const src = script.getAttribute('src');
            if (!this.isAllowedScriptSource(src)) {
                console.warn('检测到来自不可信源的脚本:', src);
            }
        });
        
        // 检查表单
        const forms = doc.querySelectorAll('form');
        forms.forEach(form => {
            const action = form.getAttribute('action');
            if (action && !this.isAllowedFormAction(action)) {
                console.warn('检测到指向不可信目标的表单:', action);
            }
        });
    }
    
    // 检查脚本源是否允许
    isAllowedScriptSource(src) {
        const allowedSources = [
            'https://trusted-cdn.com',
            'https://apis.google.com',
            'https://cdn.jsdelivr.net'
        ];
        
        return allowedSources.some(allowed => src.startsWith(allowed));
    }
    
    // 检查表单动作是否允许
    isAllowedFormAction(action) {
        const allowedActions = [
            'https://trusted-api.com',
            'https://forms.trusted-site.com'
        ];
        
        // 允许相对URL
        if (!action.startsWith('http')) {
            return true;
        }
        
        return allowedActions.some(allowed => action.startsWith(allowed));
    }
    
    // 更新沙箱策略
    updateSandboxPolicy(iframe, newTrustLevel) {
        const policies = this.sandboxPolicies[newTrustLevel] || [];
        iframe.sandbox = policies.join(' ');
        
        // 记录策略更改
        console.log(`沙箱策略已更新为: ${newTrustLevel}`);
    }
}

// 使用示例
const sandboxManager = new SandboxManager();

// 创建不同信任级别的沙箱
sandboxManager.createSandboxIframe('https://untrusted-site.com/content.html', 'untrusted');
sandboxManager.createSandboxIframe('https://semi-trusted-site.com/form.html', 'forms');
sandboxManager.createSandboxIframe('https://trusted-site.com/app.html', 'trusted');
</script>

沙箱逃逸防护

html
<!-- 防止沙箱逃逸 -->
<script>
// 沙箱逃逸检测
class SandboxEscapeDetector {
    constructor() {
        this.monitoring = true;
        this.init();
    }
    
    init() {
        // 监控iframe创建
        this.monitorIframeCreation();
        
        // 监控postMessage
        this.monitorPostMessage();
        
        // 监控URL变化
        this.monitorURLChanges();
    }
    
    // 监控iframe创建
    monitorIframeCreation() {
        const originalCreateElement = document.createElement;
        
        document.createElement = function(tagName) {
            const element = originalCreateElement.call(this, tagName);
            
            if (tagName.toLowerCase() === 'iframe') {
                // 检查沙箱属性
                const observer = new MutationObserver((mutations) => {
                    mutations.forEach((mutation) => {
                        if (mutation.type === 'attributes' && mutation.attributeName === 'sandbox') {
                            const sandbox = element.getAttribute('sandbox');
                            if (sandbox === null) {
                                console.warn('检测到移除沙箱属性的尝试');
                            } else if (sandbox.includes('allow-same-origin') && 
                                      sandbox.includes('allow-scripts')) {
                                console.warn('检测到潜在的沙箱逃逸配置');
                            }
                        }
                    });
                });
                
                observer.observe(element, {
                    attributes: true,
                    attributeFilter: ['sandbox', 'src']
                });
            }
            
            return element;
        };
    }
    
    // 监控postMessage
    monitorPostMessage() {
        const originalPostMessage = window.postMessage;
        
        window.postMessage = function(message, targetOrigin, transfer) {
            // 检查是否尝试发送敏感信息
            if (typeof message === 'object' && message !== null) {
                if (message.type === 'escape' || message.action === 'break-sandbox') {
                    console.warn('检测到潜在的沙箱逃逸尝试');
                    return;
                }
            }
            
            return originalPostMessage.call(this, message, targetOrigin, transfer);
        };
    }
    
    // 监控URL变化
    monitorURLChanges() {
        let lastURL = window.location.href;
        
        const observer = new MutationObserver(() => {
            const currentURL = window.location.href;
            if (currentURL !== lastURL) {
                this.validateURLChange(lastURL, currentURL);
                lastURL = currentURL;
            }
        });
        
        observer.observe(document, { subtree: true, childList: true });
        
        // 监控popstate事件
        window.addEventListener('popstate', (event) => {
            this.validateURLChange(lastURL, window.location.href);
        });
    }
    
    // 验证URL变化
    validateURLChange(oldURL, newURL) {
        const oldOrigin = new URL(oldURL).origin;
        const newOrigin = new URL(newURL).origin;
        
        if (oldOrigin !== newOrigin) {
            console.warn('检测到跨域导航:', { from: oldOrigin, to: newOrigin });
        }
    }
}

// 初始化沙箱逃逸检测
const escapeDetector = new SandboxEscapeDetector();
</script>

13.2.3 安全的数据存储

localStorage安全使用

html
<!-- 安全的localStorage使用 -->
<script>
// 安全存储管理器
class SecureStorageManager {
    constructor() {
        this.encryptionKey = this.generateEncryptionKey();
        this.keyPrefix = 'secure_';
        this.maxDataSize = 1024 * 1024; // 1MB限制
    }
    
    // 生成加密密钥
    generateEncryptionKey() {
        const array = new Uint8Array(32);
        crypto.getRandomValues(array);
        return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
    }
    
    // 简单的加密函数(仅作演示,实际应用应使用更强的加密)
    encrypt(data) {
        const jsonData = JSON.stringify(data);
        const encrypted = btoa(jsonData); // 实际应使用AES等算法
        return encrypted;
    }
    
    // 简单的解密函数
    decrypt(encryptedData) {
        try {
            const decrypted = atob(encryptedData);
            return JSON.parse(decrypted);
        } catch (error) {
            console.error('解密失败:', error);
            return null;
        }
    }
    
    // 安全设置数据
    setSecureItem(key, value) {
        try {
            // 验证数据大小
            const dataString = JSON.stringify(value);
            if (dataString.length > this.maxDataSize) {
                throw new Error('数据超出最大允许大小');
            }
            
            // 验证键名
            if (!this.isValidKey(key)) {
                throw new Error('无效的键名');
            }
            
            // 加密数据
            const encryptedValue = this.encrypt(value);
            
            // 添加时间戳和完整性校验
            const storageObject = {
                data: encryptedValue,
                timestamp: Date.now(),
                checksum: this.calculateChecksum(encryptedValue)
            };
            
            // 存储
            localStorage.setItem(this.keyPrefix + key, JSON.stringify(storageObject));
            
            return true;
        } catch (error) {
            console.error('存储失败:', error);
            return false;
        }
    }
    
    // 安全获取数据
    getSecureItem(key) {
        try {
            const storageKey = this.keyPrefix + key;
            const storedValue = localStorage.getItem(storageKey);
            
            if (!storedValue) {
                return null;
            }
            
            const storageObject = JSON.parse(storedValue);
            
            // 验证完整性
            if (!this.verifyChecksum(storageObject.data, storageObject.checksum)) {
                console.warn('数据完整性验证失败');
                localStorage.removeItem(storageKey);
                return null;
            }
            
            // 检查过期时间(如果需要)
            if (this.isExpired(storageObject.timestamp)) {
                localStorage.removeItem(storageKey);
                return null;
            }
            
            // 解密数据
            return this.decrypt(storageObject.data);
        } catch (error) {
            console.error('获取数据失败:', error);
            return null;
        }
    }
    
    // 验证键名
    isValidKey(key) {
        // 只允许字母数字和下划线
        const keyRegex = /^[a-zA-Z0-9_]+$/;
        return keyRegex.test(key) && key.length <= 50;
    }
    
    // 计算校验和
    calculateChecksum(data) {
        let hash = 0;
        for (let i = 0; i < data.length; i++) {
            const char = data.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash; // 转换为32位整数
        }
        return hash.toString(16);
    }
    
    // 验证校验和
    verifyChecksum(data, expectedChecksum) {
        const actualChecksum = this.calculateChecksum(data);
        return actualChecksum === expectedChecksum;
    }
    
    // 检查是否过期
    isExpired(timestamp, maxAge = 24 * 60 * 60 * 1000) { // 默认24小时
        return (Date.now() - timestamp) > maxAge;
    }
    
    // 清理过期数据
    cleanup() {
        const keys = [];
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            if (key && key.startsWith(this.keyPrefix)) {
                keys.push(key);
            }
        }
        
        keys.forEach(key => {
            try {
                const value = localStorage.getItem(key);
                const storageObject = JSON.parse(value);
                if (this.isExpired(storageObject.timestamp)) {
                    localStorage.removeItem(key);
                    console.log('已清理过期数据:', key);
                }
            } catch (error) {
                // 清理损坏的数据
                localStorage.removeItem(key);
                console.log('已清理损坏数据:', key);
            }
        });
    }
    
    // 安全删除数据
    removeSecureItem(key) {
        const storageKey = this.keyPrefix + key;
        localStorage.removeItem(storageKey);
    }
}

// 使用示例
const secureStorage = new SecureStorageManager();

// 存储敏感数据
secureStorage.setSecureItem('user_preferences', {
    theme: 'dark',
    language: 'zh-CN',
    notifications: true
});

// 获取数据
const preferences = secureStorage.getSecureItem('user_preferences');
console.log('用户偏好:', preferences);

// 定期清理
setInterval(() => {
    secureStorage.cleanup();
}, 60 * 60 * 1000); // 每小时清理一次
</script>

sessionStorage安全使用

javascript
// 安全的sessionStorage管理
class SecureSessionManager {
    constructor() {
        this.sessionId = this.generateSessionId();
        this.init();
    }
    
    init() {
        // 检查会话完整性
        this.validateSession();
        
        // 监控页面离开
        this.setupBeforeUnload();
        
        // 监控会话超时
        this.setupSessionTimeout();
    }
    
    generateSessionId() {
        return crypto.getRandomValues(new Uint32Array(4)).join('-');
    }
    
    // 验证会话
    validateSession() {
        const storedSessionId = sessionStorage.getItem('sessionId');
        
        if (!storedSessionId) {
            // 新会话
            sessionStorage.setItem('sessionId', this.sessionId);
            sessionStorage.setItem('sessionStart', Date.now().toString());
        } else if (storedSessionId !== this.sessionId) {
            // 会话ID不匹配,可能是攻击
            console.warn('会话ID不匹配,清理会话数据');
            this.clearSession();
        }
    }
    
    // 安全设置会话数据
    setSessionData(key, value) {
        try {
            // 验证会话有效性
            if (!this.isValidSession()) {
                throw new Error('会话无效');
            }
            
            // 创建会话数据对象
            const sessionData = {
                value: value,
                timestamp: Date.now(),
                sessionId: this.sessionId
            };
            
            sessionStorage.setItem(key, JSON.stringify(sessionData));
            return true;
        } catch (error) {
            console.error('设置会话数据失败:', error);
            return false;
        }
    }
    
    // 安全获取会话数据
    getSessionData(key) {
        try {
            const data = sessionStorage.getItem(key);
            if (!data) {
                return null;
            }
            
            const sessionData = JSON.parse(data);
            
            // 验证会话ID
            if (sessionData.sessionId !== this.sessionId) {
                console.warn('会话数据ID不匹配');
                sessionStorage.removeItem(key);
                return null;
            }
            
            return sessionData.value;
        } catch (error) {
            console.error('获取会话数据失败:', error);
            return null;
        }
    }
    
    // 验证会话有效性
    isValidSession() {
        const sessionStart = sessionStorage.getItem('sessionStart');
        if (!sessionStart) {
            return false;
        }
        
        const startTime = parseInt(sessionStart);
        const maxAge = 30 * 60 * 1000; // 30分钟
        
        return (Date.now() - startTime) < maxAge;
    }
    
    // 清理会话
    clearSession() {
        sessionStorage.clear();
        this.sessionId = this.generateSessionId();
        sessionStorage.setItem('sessionId', this.sessionId);
        sessionStorage.setItem('sessionStart', Date.now().toString());
    }
    
    // 设置页面离开监听
    setupBeforeUnload() {
        window.addEventListener('beforeunload', () => {
            // 清理敏感数据
            this.clearSensitiveData();
        });
    }
    
    // 设置会话超时
    setupSessionTimeout() {
        setInterval(() => {
            if (!this.isValidSession()) {
                console.log('会话已超时,清理数据');
                this.clearSession();
            }
        }, 60 * 1000); // 每分钟检查一次
    }
    
    // 清理敏感数据
    clearSensitiveData() {
        const sensitiveKeys = ['password', 'token', 'credit_card'];
        sensitiveKeys.forEach(key => {
            sessionStorage.removeItem(key);
        });
    }
}

// 使用示例
const sessionManager = new SecureSessionManager();

// 设置会话数据
sessionManager.setSessionData('user_id', '12345');
sessionManager.setSessionData('temp_data', { step: 1, form_data: {} });

// 获取会话数据
const userId = sessionManager.getSessionData('user_id');
console.log('用户ID:', userId);

13.2.4 安全的API使用

Web Workers安全使用

html
<!-- 安全的Web Workers使用 -->
<script>
// 安全的Web Worker管理
class SecureWorkerManager {
    constructor() {
        this.workers = new Map();
        this.trustedScripts = new Set([
            '/js/workers/data-processor.js',
            '/js/workers/crypto-worker.js'
        ]);
    }
    
    // 创建安全的Worker
    createSecureWorker(scriptPath, workerName) {
        // 验证脚本路径
        if (!this.trustedScripts.has(scriptPath)) {
            throw new Error('不可信的Worker脚本路径');
        }
        
        try {
            const worker = new Worker(scriptPath);
            
            // 设置错误处理
            worker.addEventListener('error', (event) => {
                console.error('Worker错误:', event);
                this.terminateWorker(workerName);
            });
            
            // 设置消息验证
            worker.addEventListener('message', (event) => {
                this.validateWorkerMessage(event, workerName);
            });
            
            // 设置超时机制
            const timeoutId = setTimeout(() => {
                console.warn('Worker响应超时,终止Worker');
                this.terminateWorker(workerName);
            }, 30000); // 30秒超时
            
            this.workers.set(workerName, {
                worker: worker,
                timeoutId: timeoutId,
                createdAt: Date.now()
            });
            
            return worker;
        } catch (error) {
            console.error('创建Worker失败:', error);
            throw error;
        }
    }
    
    // 验证Worker消息
    validateWorkerMessage(event, workerName) {
        const data = event.data;
        
        // 验证消息格式
        if (!data || typeof data !== 'object') {
            console.warn('收到无效的Worker消息格式');
            return;
        }
        
        // 验证消息类型
        if (!data.type || typeof data.type !== 'string') {
            console.warn('Worker消息缺少类型字段');
            return;
        }
        
        // 验证消息来源
        if (data.workerId !== workerName) {
            console.warn('Worker消息来源验证失败');
            return;
        }
        
        // 处理消息
        this.handleWorkerMessage(data, workerName);
    }
    
    // 处理Worker消息
    handleWorkerMessage(data, workerName) {
        const workerInfo = this.workers.get(workerName);
        if (!workerInfo) {
            console.warn('未找到Worker信息');
            return;
        }
        
        // 清除超时
        clearTimeout(workerInfo.timeoutId);
        
        switch (data.type) {
            case 'result':
                console.log('Worker结果:', data.payload);
                break;
            case 'error':
                console.error('Worker错误:', data.error);
                break;
            case 'progress':
                console.log('Worker进度:', data.progress);
                break;
            default:
                console.warn('未知的Worker消息类型:', data.type);
        }
    }
    
    // 发送安全消息到Worker
    sendSecureMessage(workerName, message) {
        const workerInfo = this.workers.get(workerName);
        if (!workerInfo) {
            throw new Error('Worker不存在');
        }
        
        // 创建安全消息对象
        const secureMessage = {
            id: crypto.getRandomValues(new Uint32Array(1))[0],
            timestamp: Date.now(),
            data: message
        };
        
        workerInfo.worker.postMessage(secureMessage);
    }
    
    // 终止Worker
    terminateWorker(workerName) {
        const workerInfo = this.workers.get(workerName);
        if (workerInfo) {
            clearTimeout(workerInfo.timeoutId);
            workerInfo.worker.terminate();
            this.workers.delete(workerName);
            console.log('Worker已终止:', workerName);
        }
    }
    
    // 清理所有Worker
    cleanup() {
        this.workers.forEach((workerInfo, workerName) => {
            this.terminateWorker(workerName);
        });
    }
}

// 使用示例
const workerManager = new SecureWorkerManager();

// 创建安全Worker
try {
    const dataWorker = workerManager.createSecureWorker('/js/workers/data-processor.js', 'dataProcessor');
    
    // 发送安全消息
    workerManager.sendSecureMessage('dataProcessor', {
        action: 'process',
        data: [1, 2, 3, 4, 5]
    });
} catch (error) {
    console.error('Worker创建失败:', error);
}

// 页面卸载时清理
window.addEventListener('beforeunload', () => {
    workerManager.cleanup();
});
</script>

Geolocation API安全使用

javascript
// 安全的地理位置API使用
class SecureGeolocation {
    constructor() {
        this.lastPosition = null;
        this.watchId = null;
        this.privacyMode = true;
    }
    
    // 安全获取当前位置
    async getCurrentPosition(options = {}) {
        return new Promise((resolve, reject) => {
            // 检查浏览器支持
            if (!navigator.geolocation) {
                reject(new Error('浏览器不支持地理位置API'));
                return;
            }
            
            // 设置默认选项
            const defaultOptions = {
                enableHighAccuracy: false, // 默认不使用高精度
                timeout: 10000, // 10秒超时
                maximumAge: 300000 // 5分钟缓存
            };
            
            const mergedOptions = { ...defaultOptions, ...options };
            
            // 获取位置
            navigator.geolocation.getCurrentPosition(
                (position) => {
                    const sanitizedPosition = this.sanitizePosition(position);
                    this.lastPosition = sanitizedPosition;
                    resolve(sanitizedPosition);
                },
                (error) => {
                    console.error('地理位置获取失败:', error);
                    reject(this.handleGeolocationError(error));
                },
                mergedOptions
            );
        });
    }
    
    // 清理位置数据
    sanitizePosition(position) {
        const coords = position.coords;
        
        // 根据隐私模式调整精度
        if (this.privacyMode) {
            return {
                latitude: this.reduceAccuracy(coords.latitude),
                longitude: this.reduceAccuracy(coords.longitude),
                accuracy: Math.max(coords.accuracy, 100), // 至少100米精度
                timestamp: position.timestamp
            };
        }
        
        return {
            latitude: coords.latitude,
            longitude: coords.longitude,
            accuracy: coords.accuracy,
            timestamp: position.timestamp
        };
    }
    
    // 降低精度
    reduceAccuracy(coordinate) {
        // 四舍五入到小数点后3位(约111米精度)
        return Math.round(coordinate * 1000) / 1000;
    }
    
    // 处理地理位置错误
    handleGeolocationError(error) {
        switch (error.code) {
            case error.PERMISSION_DENIED:
                return new Error('用户拒绝了地理位置访问');
            case error.POSITION_UNAVAILABLE:
                return new Error('位置信息不可用');
            case error.TIMEOUT:
                return new Error('获取位置超时');
            default:
                return new Error('获取位置时发生未知错误');
        }
    }
    
    // 监控位置变化
    watchPosition(callback, options = {}) {
        if (this.watchId) {
            this.clearWatch();
        }
        
        const safeCallback = (position) => {
            const sanitizedPosition = this.sanitizePosition(position);
            
            // 检查位置是否有显著变化
            if (this.hasSignificantChange(sanitizedPosition)) {
                callback(sanitizedPosition);
                this.lastPosition = sanitizedPosition;
            }
        };
        
        const errorCallback = (error) => {
            console.error('位置监控错误:', error);
            callback(null, this.handleGeolocationError(error));
        };
        
        this.watchId = navigator.geolocation.watchPosition(
            safeCallback,
            errorCallback,
            options
        );
        
        return this.watchId;
    }
    
    // 检查位置是否有显著变化
    hasSignificantChange(newPosition) {
        if (!this.lastPosition) {
            return true;
        }
        
        const distance = this.calculateDistance(
            this.lastPosition.latitude,
            this.lastPosition.longitude,
            newPosition.latitude,
            newPosition.longitude
        );
        
        // 超过50米才认为是显著变化
        return distance > 50;
    }
    
    // 计算两点间距离
    calculateDistance(lat1, lon1, lat2, lon2) {
        const R = 6371e3; // 地球半径(米)
        const φ1 = lat1 * Math.PI / 180;
        const φ2 = lat2 * Math.PI / 180;
        const Δφ = (lat2 - lat1) * Math.PI / 180;
        const Δλ = (lon2 - lon1) * Math.PI / 180;
        
        const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
                  Math.cos(φ1) * Math.cos(φ2) *
                  Math.sin(Δλ/2) * Math.sin(Δλ/2);
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        
        return R * c;
    }
    
    // 清除位置监控
    clearWatch() {
        if (this.watchId) {
            navigator.geolocation.clearWatch(this.watchId);
            this.watchId = null;
        }
    }
    
    // 设置隐私模式
    setPrivacyMode(enabled) {
        this.privacyMode = enabled;
    }
}

// 使用示例
const geoLocation = new SecureGeolocation();

// 获取当前位置
geoLocation.getCurrentPosition()
    .then(position => {
        console.log('当前位置:', position);
    })
    .catch(error => {
        console.error('获取位置失败:', error);
    });

// 监控位置变化
geoLocation.watchPosition((position, error) => {
    if (error) {
        console.error('位置监控错误:', error);
    } else {
        console.log('位置更新:', position);
    }
});

13.2.5 用户输入验证

客户端输入验证

html
<!-- 客户端输入验证 -->
<script>
// 输入验证器
class InputValidator {
    constructor() {
        this.patterns = {
            email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
            phone: /^1[3456789]\d{9}$/,
            url: /^https?:\/\/.+/,
            alphanumeric: /^[a-zA-Z0-9]+$/,
            numeric: /^\d+$/,
            chinese: /^[\u4e00-\u9fa5]+$/
        };
        
        this.maxLengths = {
            username: 20,
            password: 50,
            email: 100,
            phone: 20,
            address: 200
        };
    }
    
    // 验证邮箱
    validateEmail(email) {
        if (!email || typeof email !== 'string') {
            return { valid: false, message: '邮箱不能为空' };
        }
        
        if (email.length > this.maxLengths.email) {
            return { valid: false, message: '邮箱长度过长' };
        }
        
        if (!this.patterns.email.test(email)) {
            return { valid: false, message: '邮箱格式不正确' };
        }
        
        return { valid: true, message: '邮箱格式正确' };
    }
    
    // 验证密码
    validatePassword(password) {
        if (!password || typeof password !== 'string') {
            return { valid: false, message: '密码不能为空' };
        }
        
        if (password.length < 8) {
            return { valid: false, message: '密码长度至少8位' };
        }
        
        if (password.length > this.maxLengths.password) {
            return { valid: false, message: '密码长度过长' };
        }
        
        // 检查密码强度
        const hasLower = /[a-z]/.test(password);
        const hasUpper = /[A-Z]/.test(password);
        const hasDigit = /\d/.test(password);
        const hasSpecial = /[!@#$%^&*]/.test(password);
        
        const strength = [hasLower, hasUpper, hasDigit, hasSpecial].filter(Boolean).length;
        
        if (strength < 3) {
            return { valid: false, message: '密码强度不够,需包含大小写字母、数字和特殊字符中的至少3种' };
        }
        
        return { valid: true, message: '密码强度符合要求' };
    }
    
    // 验证手机号
    validatePhone(phone) {
        if (!phone || typeof phone !== 'string') {
            return { valid: false, message: '手机号不能为空' };
        }
        
        if (!this.patterns.phone.test(phone)) {
            return { valid: false, message: '手机号格式不正确' };
        }
        
        return { valid: true, message: '手机号格式正确' };
    }
    
    // 验证URL
    validateURL(url) {
        if (!url || typeof url !== 'string') {
            return { valid: false, message: 'URL不能为空' };
        }
        
        try {
            new URL(url);
            
            if (!this.patterns.url.test(url)) {
                return { valid: false, message: '只允许HTTP/HTTPS协议' };
            }
            
            return { valid: true, message: 'URL格式正确' };
        } catch (error) {
            return { valid: false, message: 'URL格式不正确' };
        }
    }
    
    // 防止XSS的HTML清理
    sanitizeHTML(html) {
        if (!html || typeof html !== 'string') {
            return '';
        }
        
        // 移除脚本标签
        html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
        
        // 移除事件处理程序
        html = html.replace(/on\w+\s*=\s*"[^"]*"/g, '');
        html = html.replace(/on\w+\s*=\s*'[^']*'/g, '');
        
        // 移除javascript:协议
        html = html.replace(/href\s*=\s*"javascript:[^"]*"/gi, 'href="#"');
        html = html.replace(/href\s*=\s*'javascript:[^']*'/gi, "href='#'");
        
        // 转义HTML特殊字符
        const entityMap = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#39;'
        };
        
        return html.replace(/[&<>"']/g, (match) => entityMap[match]);
    }
    
    // 验证文件上传
    validateFile(file, options = {}) {
        if (!file || !(file instanceof File)) {
            return { valid: false, message: '请选择文件' };
        }
        
        const defaultOptions = {
            maxSize: 10 * 1024 * 1024, // 10MB
            allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'],
            allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.pdf']
        };
        
        const mergedOptions = { ...defaultOptions, ...options };
        
        // 检查文件大小
        if (file.size > mergedOptions.maxSize) {
            return { valid: false, message: '文件大小超出限制' };
        }
        
        // 检查文件类型
        if (!mergedOptions.allowedTypes.includes(file.type)) {
            return { valid: false, message: '文件类型不被允许' };
        }
        
        // 检查文件扩展名
        const fileName = file.name.toLowerCase();
        const hasValidExtension = mergedOptions.allowedExtensions.some(ext => 
            fileName.endsWith(ext)
        );
        
        if (!hasValidExtension) {
            return { valid: false, message: '文件扩展名不被允许' };
        }
        
        return { valid: true, message: '文件验证通过' };
    }
    
    // 通用字符串验证
    validateString(value, fieldName, options = {}) {
        if (!value || typeof value !== 'string') {
            return { valid: false, message: `${fieldName}不能为空` };
        }
        
        const trimmedValue = value.trim();
        
        // 检查长度
        if (options.minLength && trimmedValue.length < options.minLength) {
            return { valid: false, message: `${fieldName}长度至少${options.minLength}位` };
        }
        
        if (options.maxLength && trimmedValue.length > options.maxLength) {
            return { valid: false, message: `${fieldName}长度最多${options.maxLength}位` };
        }
        
        // 检查模式
        if (options.pattern && !options.pattern.test(trimmedValue)) {
            return { valid: false, message: `${fieldName}格式不正确` };
        }
        
        return { valid: true, message: `${fieldName}验证通过` };
    }
}

// 使用示例
const validator = new InputValidator();

// 验证邮箱
const emailResult = validator.validateEmail('user@example.com');
console.log('邮箱验证:', emailResult);

// 验证密码
const passwordResult = validator.validatePassword('MyPassword123!');
console.log('密码验证:', passwordResult);

// 清理HTML
const cleanHTML = validator.sanitizeHTML('<p>Hello <script>alert("XSS")</script></p>');
console.log('清理后的HTML:', cleanHTML);
</script>

本节要点回顾

  • 同源策略:理解和正确实施同源策略,防止跨域攻击
  • 沙箱属性:使用iframe沙箱属性限制不可信内容的权限
  • 安全数据存储:加密存储敏感数据,验证数据完整性
  • 安全API使用:正确使用Web Workers、Geolocation等API
  • 输入验证:实施全面的客户端输入验证和数据清理

相关学习资源

常见问题FAQ

Q: 什么是同源策略?

A: 同源策略是Web安全的基本原则,要求协议、域名和端口都相同才能互相访问资源,防止恶意网站访问其他网站的敏感数据。

Q: iframe沙箱属性有什么用?

A: iframe沙箱属性可以限制嵌入内容的权限,防止恶意代码执行脚本、提交表单、访问同源数据等。

Q: 如何安全地存储敏感数据?

A: 不要在客户端存储敏感数据,如必须存储,应使用加密、完整性校验、过期时间等安全措施。

Q: 客户端验证是否足够?

A: 客户端验证只能提供用户体验,不能替代服务器端验证。所有关键验证必须在服务器端进行。

Q: 如何防护XSS攻击?

A: 通过输入验证、输出编码、使用CSP、避免innerHTML等方式防护XSS攻击。


下一节预览:下一节我们将学习安全最佳实践,重点介绍输入验证、输出编码、安全的表单处理和文件上传等实践方法。