前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

同源策略:为什么XMLHttpRequst不能跨域请求资源|浏览器篇

# 同源策略:为什么XMLHttpRequst不能跨域请求资源

30 秒速记

  • 同源策略的核心目标是隔离不同站点的权限,防止恶意页面直接控制可信页面或窃取其数据。
  • 如果缺少隔离,恶意站点可能修改银行页面的 DOM 和 CSSOM、注入脚本,并获取登录凭据。
  • 页面数据同样需要保护,否则 Cookie、IndexedDB 等内容可能被读取并上传到攻击者服务器。
  • XMLHttpRequest 默认受同源策略约束,因为任意跨源读取会扩大敏感数据泄露和伪造业务操作的风险。
  • 该策略并非封闭整个 Web,而是为跨站访问建立默认拒绝、按规则开放的安全边界。

XMLHttpRequest 默认不能跨域读取资源,是因为浏览器需要阻止恶意页面借用用户的登录状态窃取其他站点的数据。 如果没有同源限制,用户打开恶意页面后,脚本就可能读取银行、邮箱等站点的敏感响应。请求有时确实已经发出,但脚本能否读取响应是另一回事。可信服务端可以通过跨源规则明确授权,而不是由前端直接关闭这层保护。

通过前面 6 个模块的介绍,我们已经大致知道浏览器是怎么工作的了,也了解这种工作方式对前端产生了什么样的影响。在这个过程中,我们还穿插介绍了一些浏览器安全相关的内容,不过都比较散,所以最后的 5 篇文章,我们就来系统地介绍下浏览器安全相关的内容。

浏览器安全可以分为三大块——Web 页面安全、浏览器网络安全和浏览器系统安全,所以本模块我们就按照这个思路来做介绍。鉴于页面安全的重要性,我们会用三篇文章来介绍该部分的知识;网络安全和系统安全则分别用一篇来介绍。

今天我们就先来分析页面中的安全策略,不过在开始之前,我们先来做个假设,如果页面中没有安全策略的话,Web 世界会是什么样子的呢?

Web 世界会是开放的,任何资源都可以接入其中,我们的网站可以加载并执行别人网站的脚本文件、图片、音频 / 视频等资源,甚至可以下载其他站点的可执行文件。

Web 世界是开放的,这很符合 Web 理念。但如果 Web 世界是绝对自由的,那么页面行为将没有任何限制,这会造成无序或者混沌的局面,出现很多不可控的问题。

比如你打开了一个银行站点,然后又一不小心打开了一个恶意站点,如果没有安全措施,恶意站点就可以做很多事情:

  • 修改银行站点的 DOM、CSSOM 等信息;
  • 在银行站点内部插入 JavaScript 脚本;
  • 劫持用户登录的用户名和密码;
  • 读取银行站点的 Cookie、IndexDB 等数据;
  • 甚至还可以将这些信息上传至自己的服务器,这样就可以在你不知情的情况下伪造一些转账请求等信息。

所以说,在没有安全保障的 Web 世界中,我们是没有隐私的,因此需要安全策略来保障我们的隐私和数据的安全。

这就引出了页面中最基础、最核心的安全策略:同源策略(Same-origin policy)。

原理拆解: 浏览器把协议、主机和端口共同作为源的边界。页面脚本可以自由操作本源的 DOM、存储和网络响应,但默认不能读取另一源页面的 DOM,也不能通过 XMLHttpRequest 任意取得另一源响应内容。否则恶意页面只需诱导已登录用户打开,就可能借用浏览器中的登录状态读取银行、邮箱等站点的私有数据。同源策略主要限制“读取和控制”,并不意味着所有跨源动作都被禁止:页面仍可加载跨源图片、提交表单或发出某些请求,只是脚本通常拿不到受保护的响应。

最小验证: 在任意非 https://example.com 页面的开发者工具中运行以下 JavaScript;输入是跨源地址,要验证 XMLHttpRequest 不能直接读取未授权的跨源响应:

const target = "https://example.com/";
const xhr = new XMLHttpRequest();

xhr.open("GET", target, true);
xhr.onload = () => {
  console.log("status:", xhr.status);
  console.log("body:", xhr.responseText.slice(0, 80));
};
xhr.onerror = () => {
  console.log("跨源响应被浏览器阻止,脚本无法读取内容");
};
xhr.send();
@前端进阶之旅: 代码已经复制到剪贴板

open 指定跨源请求,responseText 是代码试图读取的受保护内容。目标服务器未按跨源访问规则授权当前源时,预期触发 onerror,控制台还会出现同源或跨源资源共享相关错误;网络面板中仍可能看到请求,说明“请求曾发出”和“脚本可以读取响应”是两件事。若当前页面本身就是 https://example.com,该输入不再跨源,验证条件便不成立。

边界与排查: 跨源访问并非绝对禁止,可信服务器可以按规则明确开放指定来源;但开放规则属于受控授权,不能通过前端关闭同源策略来代替。排查时应同时比较两个 URL 的协议、主机和端口,并查看浏览器控制台与响应头。服务端日志显示收到请求,却不代表页面获得了响应数据;反过来,表单能跨站提交或图片能显示,也不能证明 XMLHttpRequest 拥有跨源读取权限。

面试官追问

追问 1用户同时打开已登录的银行页面和恶意广告页,安全评审说“广告页看不到银行界面,所以最多只能加载一张图片”,这个风险判断漏掉了什么?
参考回答

若没有页面安全策略,恶意页不仅可能操作银行页的 DOM、注入脚本,还可能读取凭据与站点数据并上传到自己的服务器。风险会延伸到借用登录状态伪造业务请求;不能把安全边界只理解为视觉隔离。

追问 2商品页通过 XMLHttpRequest 请求 https://example.com/,网络面板显示请求已到达且返回 200,但 responseText 读取失败,前端应怎样解释?
参考回答

请求到达与脚本获得响应是两件事,目标服务器未授权当前源时,浏览器仍会阻止脚本读取跨源内容。应查看控制台的同源或跨源错误及相关响应头;不能因为服务端日志有记录就把故障归因于网络中断。

追问 3测试人员在 https://example.com 页面运行跨源验证脚本,发现能够读取 https://example.com/ 的响应,于是报告同源策略失效,你如何判断验证条件?
参考回答

该输入并未跨源,因此读取成功不能证明同源策略失效。应换到协议、主机或端口不同的页面重新执行,并确认目标服务器没有明确授权;测试环境改变源边界后,预期现象也会随之改变。

追问 4线上跨域接口偶发报错,服务端确认每次都收到请求,浏览器却只在部分部署环境触发 onerror,你会核对哪些证据?
参考回答

应逐一比较页面与接口的协议、主机和端口,再查看控制台报错、响应头及脚本读取结果。还要确认不同环境是否配置了不同的跨源授权;服务端收到请求不代表浏览器允许页面访问响应,表单或图片可用也不能作为反证。

追问 5平台团队提议关闭浏览器同源限制,理由是多个业务域都要读取用户中心接口,你会给出什么替代方向?
参考回答

不应以关闭客户端安全边界解决部署便利性,应由可信服务器按规则向必要来源开放跨源读取。授权范围需要与业务调用方对应,并保留对跨源 DOM 和存储的隔离;开放一个接口不等于允许对方控制整个站点。

# 什么是同源策略

30 秒速记

  • 判断两个 URL 是否同源,只比较协议、域名和端口;路径与查询参数不参与判断。
  • 同源页面默认可以访问彼此资源并操作 DOM,不同源页面则受到浏览器安全规则限制。
  • 在文档对象层,同源策略阻止跨源脚本随意读写另一个页面的 DOM。
  • 在存储层,它隔离不同源的 Cookie、IndexedDB 和 LocalStorage,即使持有另一个窗口的 opener 也不能据此越权读取。
  • 在网络层,XMLHttpRequest 等接口默认不能直接读取跨源资源,以避免站点数据被任意来源获取。

同源策略是浏览器按协议、域名和端口划分权限边界的一套安全规则。 三者都相同才算同源,路径和查询参数不同不会改变源。不同源的脚本默认不能随意操作对方的 DOM、读取 Cookie 等存储,也不能通过 XMLHttpRequest 直接拿到跨源响应。即使获得另一个窗口的 opener,也不能借此越权读取它的数据。

要了解什么是同源策略,我们得先来看看什么是同源。

如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源。比如下面这两个 URL,它们具有相同的协议 HTTPS、相同的域名 time.geekbang.org,以及相同的端口 443,所以我们就说这两个 URL 是同源的。

https://time.geekbang.org/?category=1
https://time.geekbang.org/?category=0
@前端进阶之旅: 代码已经复制到剪贴板

浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略。

具体来讲,同源策略主要表现在 DOM、Web 数据和网络这三个层面。

第一个,DOM 层面。同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。

第二个,数据层面。同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据。由于同源策略,我们依然无法通过第二个页面的 opener 来访问第一个页面中的 Cookie、IndexDB 或者 LocalStorage 等内容。你可以自己试一下,这里我们就不做演示了。

第三个,网络层面。同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。你还记得在《17 | WebAPI:XMLHttpRequest 是怎么实现的?》这篇文章的末尾分析的 XMLHttpRequest 在使用过程中所遇到的坑吗?其中第一个坑就是在默认情况下不能访问跨域的资源。

← HTTP3:甩掉TCP、TCL包袱 构建高效网络跨站脚本攻击XSS:为什么cookie中有httpOnly属性 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础