面试精讲-同源策略

同源策略:是浏览器的一个安全策略。

主要是为了防范我们的恶意网站读取我们的数据

  • 跨域DOM操作:直接读取iframe银行网站的DOM。
js 复制代码
<!-- 尝试嵌入银行网站的 iframe -->
<iframe id="bankFrame" src="https://bank.com/login"></iframe>

<script>
// 尝试读取 iframe 中的 DOM
const iframe = document.getElementById('bankFrame');
try {
  // 这里会抛出安全错误
  const bankInput = iframe.contentDocument.getElementById('password');
  console.log('窃取到密码输入框:', bankInput); // 永远执行不到这里
} catch (e) {
  console.error('同源策略阻止了DOM访问:', e.message);
  // 输出:SecurityError: Blocked a frame with origin "http://evil.com" from accessing a cross-origin frame.
}
</script>
  • 阻止js读取fetch响应内容。
js 复制代码
// 尝试请求银行API
fetch('https://bank.com/api/balance')
  .then(response => {
    // 这里永远无法读取到实际响应
    console.log('响应状态:', response.status); // 可能显示200
    return response.json();
  })
  .then(data => {
    console.log('窃取到余额数据:', data); // 永远不会执行到这里
  })
  .catch(error => {
    console.error('同源策略阻止了响应读取:', error);
    // 输出:TypeError: Failed to fetch (实际响应已被浏览器屏蔽)
  });

其实我们的请求会到达服务器,只是在浏览器端进行了跨域拦截。 因此仍然会存在一些问题,例如CSRF攻击。 因此在发出一些复杂请求(非简单)时,会预先发出预检Option请求,来获取跨域操作信息,决定能否对服务器进行发出请求。

简单请求有如下: Get请求 Head请求 post请求(不含自定义请求头, content-Type为 application/x-www-unloadedcoded multipateform-data text/plain)

Option请求

预检请求有缓存机制 Access-Control-Max-Age 存在内存中(关闭即清除) 隐私模式不进行缓存。

当预检请求通过,才会发送复杂请求,不通过报同源错误。

解决跨域的方案

jsonP

首先我们会在客户端定义一个函数,接着服务器返回的js中调用这个回调函数,需要后端配合。

js 复制代码
    res.end('callback('+data+')');
js 复制代码
//前端
   funstion jsonp(url,back){
   const script = document.createElement('script');
   script.src =url;
   document.appendChild(script);
   window.callaback = callback;
   }
   
   jsonp('http://localhost:8080',function(data){
   console.log(data);
   }
   );

CORS

后端可以给出响应头,告诉哪些东西能够访问。

当遇到简单请求头,不需要预检。当遇到复杂请求头,需要设置服务器哪些请求头允许访问。

js 复制代码
app.use((req, res, next) => {
    console.log('中间件来了')
    console.log(req.headers)
    res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5503')
    next()
})

//处理OPTIONS预检请求
app.options('*', (req, res) => {
    res.setHeader('Access-Control-Max-Age', '86400')// 设置缓存
    res.setHeader('Access-Control-Allow-Methods', 'POST') // 设置允许方式,针对复杂请求
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type') // 设置响应头,针对复杂响应头
    res.sendStatus(204)
})

app.get('/', (req, res) => {
    res.send('hello world')
})

app.post('/',(req,res)=>{
    console.log('Post来了')
    res.send('hello world')
})

注意: 我们的 Access-Control-Allow-Header Access-Control-Allow-Methods 只要设置在Option请求即可,发现没,他是专门为我们的复杂数据预检准备的,并且只需要在这里设置之后,在post的接收中就不必再设置了。

postMessage

他解决的并不是浏览器与服务器之间的跨域通信,而是不同浏览器窗口(iframe,tab)的跨域通信问题。

  1. postMessage 完全在浏览器内部工作,用于不同窗口、iframe 或标签页之间的通信
  2. 通信过程中的数据不会经过服务器,也不需要服务器参与或配置
  3. 即使是跨域通信,也只是浏览器绕过了同源策略的限制,允许不同源的窗口互相发送消息
  4. 所有消息传递都在客户端完成,数据不会通过网络发送到服务器
js 复制代码
// 在domainA.com页面中
const otherTab = window.open('https://domainB.com/page');

// 等待新页面加载完成后发送消息
setTimeout(() => {
  otherTab.postMessage('来自domainA的消息', 'https://domainB.com');
}, 1000);

// 在domainB.com页面中
window.addEventListener('message', (event) => {
  // 检查消息来源
  console.log('消息来源:', event.origin);
  console.log('消息内容:', event.data);
  
  // 回复消息到源页面
  if (event.origin === 'https://domainA.com') {
    event.source.postMessage('已收到你的消息', event.origin);
  }
});
js 复制代码
<!-- 在domainA.com -->
<iframe id="communicator" src="https://domainB.com/bridge.html" style="display:none;"></iframe>
<script>
  const iframe = document.getElementById('communicator');
  
  // 确保iframe加载完成
  iframe.onload = function() {
    // 向iframe发送跨域消息
    iframe.contentWindow.postMessage('发送到domainB的消息', 'https://domainB.com');
  };
  
  // 接收来自iframe的消息
  window.addEventListener('message', function(event) {
    if (event.origin === 'https://domainB.com') {
      console.log('收到来自domainB的回复:', event.data);
    }
  });
</script>

<!-- 在domainB.com/bridge.html -->
<script>
  // 监听来自父页面的消息
  window.addEventListener('message', function(event) {
    if (event.origin === 'https://domainA.com') {
      console.log('收到来自domainA的消息:', event.data);
      
      // 回复消息
      event.source.postMessage('domainB已收到消息', event.origin);
      
      // 如果需要,还可以转发消息到domainB的其他页面
      // 比如通过localStorage或其他方式
    }
  });
</script>
js 复制代码
// 在被打开的页面(domainB.com)中
if (window.opener) {
  window.opener.postMessage('这是来自新标签页的消息', 'https://domainA.com');
}

// 接收来自opener的消息
window.addEventListener('message', (event) => {
  if (event.origin === 'https://domainA.com') {
    console.log('收到opener消息:', event.data);
  }
});

注意:他必须得获取窗口的引用,比如通过window.open , iframe标签 得到引用才能进行通信

代理配置 vite Nginx反向代理

vite代理和Nginx都是同样原理,我们的前端项目部署之后。我们的请求发送到同域名,接着做一个转发,这样就不会有跨域问题

Nginx反向代理,之所以叫反向代理,是他不暴露出服务器的网址给客户端。他还支持负载均衡,提供Http服务。错误日志,Http缓存。

websocket

TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

浏览器对localhost有特殊处理,对于同一主机名下的不同端口之间的通信,通常会采用较为宽松的跨域策略。您的前端页面可能是从 http://localhost的某个端口访问的,连接到ws://localhost:8080,这种情况下某些浏览器会允许连接。

js 复制代码
const express = require('express');
const app = express();
let WebSocketServer = require('ws').Server;
let wss = new WebSocketServer({port:8080});
wss.on('connection',function(ws){
    console.log('有新的连接');
    ws.on('message',function(msg){
        console.log(msg);
    })
})
js 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        let ws = new WebSocket('ws://localhost:8080');
        ws.onopen = function(){
            console.log('连接成功');
        }
        ws.onmessage = function(event){
            console.log(event.data);
        }
    </script>
</body>
</html>

iframe + document.domain

  • 当两个窗口(或iframe)属于同一个父域的不同子域时(如a.example.com和b.example.com
  • 通过在两个页面中都设置document.domain = 'example.com'
  • 浏览器会认为它们是"同源"的,从而解除跨域限制
  • 两个窗口就可以相互访问DOM、调用对方的方法、读写对方的变量等
js 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>主页面 - www.example.com</title>
</head>
<body>
    <h1>这是主域名页面</h1>
    
    <!-- 加载跨子域的iframe -->
    <iframe id="myFrame" src="http://api.example.com/b.html" style="width:100%;height:200px;"></iframe>
    
    <script>
        // 设置document.domain为共同的父域
        document.domain = 'example.com';
        
        // 等待iframe加载完成
        window.onload = function() {
            var frame = document.getElementById('myFrame');
            
            // 尝试访问iframe中的内容
            try {
                console.log('iframe中的标题是:', frame.contentWindow.document.title);
                
                // 调用iframe中的方法
                frame.contentWindow.sayHello('从父页面发来的消息');
                
                // 添加按钮来测试通信
                var btn = document.createElement('button');
                btn.innerHTML = '调用iframe中的方法';
                btn.onclick = function() {
                    frame.contentWindow.sayHello('按钮点击时发送的消息');
                };
                document.body.appendChild(btn);
            } catch(e) {
                console.error('访问iframe失败:', e);
            }
        };
    </script>
</body>
</html>
js 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>iframe页面 - api.example.com</title>
</head>
<body>
    <h2>这是子域名中的iframe</h2>
    
    <div id="message"></div>
    
    <script>
        // 设置document.domain为共同的父域
        document.domain = 'example.com';
        
        // 定义可被父页面调用的函数
        function sayHello(msg) {
            document.getElementById('message').innerHTML = '收到消息: ' + msg;
            
            // 可以访问父页面的方法和属性
            window.parent.document.title = '标题已被iframe修改';
            
            return '来自iframe的回应';
        }
        
        // 也可以直接调用父页面的方法或修改属性
        try {
            console.log('父页面的URL是:', window.parent.location.href);
        } catch(e) {
            console.error('访问父页面失败:', e);
        }
    </script>
</body>
</html>

添加启动命令

js 复制代码
chrome.exe --disable-web-security

CSRF 攻击

即使有了同源策略,仍然无法阻止CSRF攻击

举一个例子,假如我们的一个Tab中打开了银行网站,另一个Tab中打开了恶意网站。假如我们恶意网站中使用了 img , script , link 标签的src属性,他们会自动携带目标网址的cookie(登录凭证),直接进行转账操作。

解决措施:

1. CSRF Token(最有效的方法)

  • 原理:
  1. 生成:服务器为每个用户会话生成一个唯一、随机的token
  2. 存储:服务器将此token存储在用户的会话中
  3. 分发:服务器将token嵌入到页面中(表单隐藏字段、JavaScript变量等)
  4. 提交:用户提交请求时,token随请求一起发送
  5. 验证:服务器验证接收到的token是否与存储的匹配

注意: src属性总是会携带cookie fetch要手动指定是否携带cokie axios默认携带同源请求cookie,跨域携带cookie需要设置。表单提交会自动携带cookie。

  • 设置:Set-Cookie: session=123; SameSite=Strict(或Lax)
  • 效果:限制第三方网站发送的请求携带Cookie
  • 模式:
  • Strict:仅同站点请求携带Cookie (从外部链接返回可能导致未登录态)
  • Lax:导航到目标网站的链接(<a href>)且为安全方法(head,option,get)可以携带Cookie
  • None:需要配合Secure使用,都会携带cookie。

3. 检查请求来源

3.1 验证Referer头:确保请求来自合法域名

优势:实现简单,大多数浏览器都会发送该头

局限性:

  • 用户可能已禁用Referer头(出于隐私考虑)
  • 某些代理可能会移除或修改此头
  • 攻击者可能在某些情况下伪造Referer
  • 验证Origin头:更可靠,不受修改

3.2 验证Origin头

  • 优势:
  • 比Referer更可靠,内容更简洁
  • 用户无法通过浏览器设置修改
  • 即使在HTTPS到HTTP的请求中也会发送(而Referer可能不会)
  • 不包含路径信息,更好地保护隐私

4. 双重Cookie验证

  • 服务器设置一个cookie返回给前端
  • 在Cookie和请求参数中都包含相同的随机值
  • 服务器检查两者是否匹配

5. 关键操作二次验证

  • 敏感操作要求重新验证身份(密码、手机验证码)
  • 添加验证码(CAPTCHA)防止自动提交

总结: 不同防护机制的共同本质

所有CSRF防护机制的核心是:利用同源策略限制,依赖攻击者无法获取/设置的信息进行请求验证。

让我们看看各种CSRF防护机制如何体现这一本质:

1. 传统CSRF Token

  • 关键信息:服务器生成的随机token
  • 存储位置:服务器端会话 + 客户端页面(非Cookie)
  • 验证原理:恶意站点无法获取用户在目标网站上的token

2. 双Cookie验证

  • 关键信息:随机生成的值
  • 存储位置:Cookie + 手动添加的请求组件
  • 验证原理:恶意站点虽然能触发带Cookie的请求,但无法读取Cookie值

3. 检查请求来源

  • 关键信息:请求的Referer/Origin头
  • 存储位置:浏览器自动生成
  • 验证原理:恶意站点无法伪造合法的来源头

4. JWT(特定实现方式)

  • 关键信息:包含用户信息的签名令牌
  • 存储位置:localStorage/sessionStorage(非Cookie)
  • 验证原理:恶意站点无法访问其他域的存储

XSS攻击

XSS (Cross-Site Scripting) 跨站脚本攻击是一种常见的网络安全漏洞,攻击者通过在网页中注入恶意脚本代码,当用户浏览该页面时,恶意脚本会在用户的浏览器中执行,从而实现各种攻击目的。

反射型XSS (Reflected XSS)

  • 特点:不存储在服务器上,通常通过URL参数等方式传递
  • 过程:
  • 攻击者构造包含恶意脚本的链接
  • 用户点击链接后,服务器将恶意代码作为响应的一部分返回
  • 用户浏览器执行这段恶意代码

2. 存储型XSS (Stored XSS)

  • 特点:恶意代码被存储在服务器数据库中
  • 过程:
  • 攻击者在论坛、评论区等位置提交包含恶意代码的内容
  • 服务器将内容存储在数据库中
  • 当其他用户访问包含该内容的页面时,恶意代码被执行
  • 危害:影响范围广,所有访问页面的用户都可能受到攻击

3. DOM型XSS (DOM-based XSS)

  • 特点:完全在客户端执行,服务器不参与处理恶意数据
  • 过程:
  • 恶意代码通过URL片段(#)等方式被传入
  • 客户端JavaScript读取并处理这些数据
  • 不恰当的处理导致恶意代码被执行

解决方案:

1. CSP

他是一个浏览器端执行的安全策略,其核心工作原理:

  • 识别资源来源:浏览器会检查每个资源(脚本、样式、图片等)的来源
  • 对照白名单:将资源来源与CSP策略中允许的来源列表进行比对
  • 阻止非法资源:如果资源来自未经授权的源,浏览器会直接阻止该资源加载或执行

CSP通过HTTP响应头或HTML元标签启用:

HTTP头方式(推荐)

js 复制代码
Content-Security-Policy: default-src 'self';

元标签方式

js 复制代码
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">

可以控制的源有很多,script,图片, css等。

2.代码转义。

将含有 <script>这样的脚本进行转义,

js 复制代码
    &lt;script&gt;
    console.log("这是示例代码");
    &lt;/script&gt;

3.设置cookie为HttpOnly

这样不允许js去拿到cookie

相关推荐
Samuel-Gyx32 分钟前
前端 CSS 样式书写与选择器 基础知识
前端·css
天天打码1 小时前
Rspack:字节跳动自研 Web 构建工具-基于 Rust打造高性能前端工具链
开发语言·前端·javascript·rust·开源
字节高级特工1 小时前
【C++】”如虎添翼“:模板初阶
java·c语言·前端·javascript·c++·学习·算法
db_lnn_20212 小时前
【vue】全局组件及组件模块抽离
前端·javascript·vue.js
Qin_jiangshan2 小时前
vue实现进度条带指针
前端·javascript·vue.js
菜鸟una2 小时前
【layout组件 与 路由镶嵌】vue3 后台管理系统
前端·vue.js·elementui·typescript
小张快跑。2 小时前
【Vue3】使用vite创建Vue3工程、Vue3基本语法讲解
前端·前端框架·vue3·vite
Zhen (Evan) Wang2 小时前
.NET 8 API 实现websocket,并在前端angular实现调用
前端·websocket·.net
星空寻流年3 小时前
css3响应式布局
前端·css·css3
Rverdoser3 小时前
代理服务器运行速度慢是什么原因
开发语言·前端·php