Search K
Appearance
Appearance
关键词: HTML5安全特性, 同源策略, 沙箱属性, 安全数据存储, 安全API, 用户输入验证, Web安全, 权限控制
同源策略是Web安全的基石,它限制了一个源的文档或脚本如何与另一个源的资源进行交互。
<!-- 同源策略示例 -->
<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跨域请求示例 -->
<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><!-- 防止同源策略绕过 -->
<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><!-- 基本沙箱配置 -->
<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>// 沙箱管理器
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><!-- 防止沙箱逃逸 -->
<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><!-- 安全的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管理
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);<!-- 安全的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>// 安全的地理位置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);
}
});<!-- 客户端输入验证 -->
<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 = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
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>A: 同源策略是Web安全的基本原则,要求协议、域名和端口都相同才能互相访问资源,防止恶意网站访问其他网站的敏感数据。
A: iframe沙箱属性可以限制嵌入内容的权限,防止恶意代码执行脚本、提交表单、访问同源数据等。
A: 不要在客户端存储敏感数据,如必须存储,应使用加密、完整性校验、过期时间等安全措施。
A: 客户端验证只能提供用户体验,不能替代服务器端验证。所有关键验证必须在服务器端进行。
A: 通过输入验证、输出编码、使用CSP、避免innerHTML等方式防护XSS攻击。
下一节预览:下一节我们将学习安全最佳实践,重点介绍输入验证、输出编码、安全的表单处理和文件上传等实践方法。