前端自检神器Self检测的实战应用与踩坑总结
Self检测,我一般选这种方式
之前做安全防护的时候遇到Self检测的需求,查了不少资料发现方案还挺多的。说实话,刚开始我也搞不清哪种方式更好,折腾了好几个小时才把各个方案摸透。现在回过头看,其实几种方式差别还挺明显的,有些坑我当时就踩过。
Window.self vs iframe检测,各有千秋
最常用的两种Self检测方案,一种是直接用window.self判断,另一种是iframe检测。我比较喜欢用window.self的方式,因为代码简单,但是iframe检测有时候更准确。
先看看window.self的实现:
function checkSelf() {
if (window.self !== window.top) {
console.log('页面被嵌套了');
return false;
}
// 更严格的检测
try {
window.self.document;
return true;
} catch(e) {
console.log('跨域检测失败', e);
return false;
}
}
// 使用
if (!checkSelf()) {
alert('页面不允许被嵌套');
}
这种方案的好处是简单直接,一个判断就搞定。但是有个坑就是如果遇到特别复杂的嵌套关系,可能检测不够准确。比如有些恶意站点会用多层iframe包装,这时候window.self的判断就不够用了。
再说说iframe检测方案:
function detectFrameBusting() {
let inIframe = false;
try {
inIframe = window.self !== window.top;
// 检测是否有尝试脱离iframe的行为
if (inIframe && window.location.hostname === document.referrer.split('/')[2]) {
// 检测是否尝试修改parent.location
const originalParentLocation = window.parent.location.href;
setTimeout(() => {
if (window.parent.location.href !== originalParentLocation) {
console.log('检测到frame busting行为');
}
}, 100);
}
} catch(e) {
// 跨域情况下无法访问parent
inIframe = true;
}
return inIframe;
}
iframe检测的好处是能检测到更多细节,比如有没有frame busting行为。但是代码复杂度高了不少,而且跨域情况下能获取的信息有限。
Document.referrer配合检测,更全面
单独用window.self可能不够准确,配合document.referrer一起检测效果更好。我一般会把这两种方式结合起来。
function comprehensiveSelfCheck() {
// 基础self检测
const isNested = window.self !== window.top;
// referrer检测
const hasReferrer = document.referrer && document.referrer !== '';
const referrerDomain = hasReferrer ?
document.referrer.split('/')[2] : null;
// 当前域名
const currentDomain = window.location.hostname;
// 检测逻辑
if (isNested && hasReferrer && referrerDomain !== currentDomain) {
return {
isSafe: false,
reason: '外部嵌套',
details: { isNested, referrerDomain, currentDomain }
};
}
// 检测是否在可信域名内
const trustedDomains = ['trusted-domain.com', 'another-trusted.com'];
if (isNested && hasReferrer && !trustedDomains.includes(referrerDomain)) {
return {
isSafe: false,
reason: '非可信域名嵌套',
details: { referrerDomain }
};
}
return {
isSafe: true,
reason: '正常访问',
details: { isNested, hasReferrer, referrerDomain }
};
}
// 使用
const checkResult = comprehensiveSelfCheck();
console.log('Self检测结果:', checkResult);
if (!checkResult.isSafe) {
alert(访问异常: ${checkResult.reason});
}
这种组合检测的方式虽然代码多了点,但是准确率确实提高不少。特别是配合可信域名列表,能过滤掉大部分恶意嵌套。
CSP头部检测,额外防护
除了JavaScript检测,还可以配合CSP头部来增强防护。这个我在实际项目中用得不多,主要是考虑到兼容性问题。
// 动态检查CSP策略
function checkCSP() {
const metaTags = document.getElementsByTagName('meta');
for (let i = 0; i < metaTags.length; i++) {
if (metaTags[i].getAttribute('http-equiv') === 'Content-Security-Policy') {
const cspValue = metaTags[i].getAttribute('content');
if (cspValue && cspValue.includes('frame-ancestors')) {
// 解析frame-ancestors规则
const frameAncestors = cspValue.match(/frame-ancestorss+([^;]+)/i);
if (frameAncestors) {
return {
hasFrameAncestors: true,
allowedOrigins: frameAncestors[1].trim().split(/s+/)
};
}
}
}
}
return {
hasFrameAncestors: false,
allowedOrigins: []
};
}
// 服务端设置CSP头部
// Content-Security-Policy: frame-ancestors 'self';
说实话,CSP检测的实际效果要看浏览器支持情况,老版本浏览器基本没戏。而且动态检查CSP策略也比较复杂,一般还是建议服务端直接设置。
我的选型逻辑:简单实用优先
经过多次实践,我现在主要用window.self + document.referrer的组合。简单来说就是先用window.self判断是否被嵌套,如果是的话再用referrer判断来源是否可信。这套方案覆盖了大部分场景,而且代码量适中。
具体选择要考虑几个因素:
- 项目复杂度:简单项目用window.self就够了
- 安全要求:高安全需求建议组合检测
- 兼容性:老旧浏览器可能需要降级处理
- 维护成本:代码越复杂后期维护难度越大
对于大部分业务场景,我建议这样实现:
class SelfDetector {
constructor(options = {}) {
this.trustedOrigins = options.trustedOrigins || [];
this.checkInterval = options.checkInterval || 1000;
this.isChecking = false;
}
basicCheck() {
try {
// 基础self检测
const isTop = window.self === window.top;
if (!isTop) {
// 检测嵌套深度
let depth = 0;
let current = window;
while (current !== current.parent) {
depth++;
current = current.parent;
if (depth > 10) break; // 防止无限循环
}
return {
isSafe: false,
type: 'nested',
depth: depth
};
}
return { isSafe: true, type: 'normal' };
} catch(e) {
return {
isSafe: false,
type: 'cross_domain_access_error'
};
}
}
fullCheck() {
const basicResult = this.basicCheck();
if (!basicResult.isSafe && basicResult.type === 'nested') {
// 检查referrer是否可信
if (document.referrer) {
try {
const referrerOrigin = new URL(document.referrer).origin;
if (this.trustedOrigins.includes(referrerOrigin)) {
basicResult.isSafe = true;
basicResult.type = 'trusted_nested';
}
} catch(e) {
// referrer格式不正确,按异常处理
}
}
}
return basicResult;
}
autoCheck() {
if (this.isChecking) return;
this.isChecking = true;
setInterval(() => {
const result = this.fullCheck();
if (!result.isSafe) {
this.handleUnsafe(result);
}
}, this.checkInterval);
}
handleUnsafe(result) {
console.warn('Self检测异常:', result);
// 可以选择重定向或者提示用户
if (result.type === 'nested' || result.type === 'cross_domain_access_error') {
// 这里可以记录日志或者报警
fetch('https://jztheme.com/api/security-alert', {
method: 'POST',
body: JSON.stringify({
type: 'self_detection_violation',
details: result
})
});
}
}
}
// 使用示例
const detector = new SelfDetector({
trustedOrigins: ['https://trusted-domain.com'],
checkInterval: 2000
});
const result = detector.fullCheck();
console.log('Self检测结果:', result);
if (!result.isSafe) {
// 根据不同情况处理
if (result.type === 'nested') {
document.body.innerHTML = '<div>页面不允许被嵌套访问</div>';
}
}
这样一套下来,基本能满足大部分项目的Self检测需求。重点是要根据实际业务场景调整策略,不能盲目追求完美方案。
以上是我的对比总结,有不同看法欢迎评论区交流
Self检测这块确实没有标准答案,每种方案都有适用场景。关键是要清楚各种方案的局限性,选择最适合当前项目的方案。我踩过的坑基本都在文中提到了,希望能帮你少走点弯路。

暂无评论