Skip to content

同源策略与跨域 ​

本页对应 同源策略与跨域,共 4 段视频,约 37 分钟。

学习目标 ​

  1. 能准确说明浏览器同源策略的作用,并依据协议、域名、端口号判断两个 URL 是否同源。
  2. 能识别并解释跨域问题,能根据浏览器控制台报错信息判断请求是否因跨域失败。
  3. 能在 Express 后端项目中安装并配置 CORS,验证响应头包含 Access-Control-Allow-Origin 且前端可正常获取数据。
  4. 能区分 CORS 与同源访问方案的适用场景,说明开发阶段与上线部署阶段应如何选择。
  5. 能使用 Express 的 static 方法暴露前端静态资源目录,验证网页与其内嵌 Ajax 请求同源并可正常访问。

知识点 ​

本节目标与同源策略 ​

本节目标是:掌握跨域的概念,以及出现跨域问题时浏览器会怎样报错、我们怎样复现和解决。

为什么需要:前后端分离开发时,前端页面常由 Live Server 启动在 http://localhost:5500,后端接口却运行在 http://localhost:3000。浏览器有同源策略,会限制一个源的文档或脚本与另一个源进行数据交互,尤其是 AJAX/XHR 请求。

怎么想:先记住“源”的组成:协议、域名、端口号。所谓同源,就是网页打开时所在源的 URL 与 AJAX 请求的 URL 中,协议、域名、端口号全部一致。转写稿里用钓鱼网站举例:页面源和 AJAX 请求源域名不同,就不是同源,浏览器会限制请求响应。这里不要把“源”写成“圆”。

怎么做:判断跨域时只看三元组,不看资源路径。例如 http://localhost:5500 与 http://localhost:3000/api/province,协议和域名相同,但端口 5500 与 3000 不同,所以跨域。

易错点:端口不同也算跨域;资源路径 /api/province 不参与同源判断;同源策略是浏览器行为,不是服务器“坏了”。

跨域是怎么发生的 ​

为什么需要:需要先能识别跨域。跨域是“从一个源的文档/脚本,加载另一个源的资源”时产生的问题。网页文档打开时所在源和 AJAX 访问的源,只要协议、域名、端口有一个不同,就发生跨域访问。

网页源与AJAX请求源不同导致跨域示意图

怎么想:用本节需求复现:在 Live Server 的 Web 服务启动网页,用 AJAX 访问本机 Web 服务提供的省份列表接口。Live Server 通常启动在 5500,本机接口在 3000,端口不同,因此会跨域。

怎么做:index.html 中的请求代码如下:

js
// 目标:请求本机 Web 服务提供的省份列表数据
axios({
  url: 'http://localhost:3000/api/province',
  // method: 'get'
}).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})

调试时先启动 3000 的后端服务,用浏览器地址栏访问接口确认服务正常;再用 Live Server 打开前端页面,按 F12 打开开发者工具。控制台会出现类似报错:

Access to XMLHttpRequest 意思为:XHR 链接出了问题,从一个源(http://localhost:5500 跨域访问 http://localhost:3000)

浏览器控制台跨域报错示意图

易错点:不要只看到 axios 的 catch 就以为接口写错;先看 Console 是否有 Access to XMLHttpRequest 这类跨域报错。转写稿强调,判断跨域时不需要考虑资源路径,因为源里只有协议、域名、端口号。

用 CORS 解决开发阶段跨域 ​

为什么需要:前后端分离项目里,前端和后端代码不在同一个源下,但数据通信还要正常进行。开发阶段常用 CORS(跨域资源共享)解决。

怎么想:CORS 是基于 HTTP 头的机制,核心是让服务器端设置响应头字段 Access-Control-Allow-Origin,标记允许其他源访问服务器资源。前端不需要额外操作,仍正常用 axios 发请求、接收响应。

怎么做:后端先下载固定名字的 cors 软件包,再导入函数,并把它交给 Express 的 Web 服务对象:

bash
npm i cors
js
const cors = require('cors')
server.use(cors())

转写稿中的调试过程是:启动 3000 后端,在浏览器打开接口并按 F12 查看 Network 响应头,能看到 Access-Control-Allow-Origin 字段;再用 Live Server 从 5500 打开前端,Console 中原来的跨域报错消失,axios 能拿到省份列表数据。

易错点:CORS 主要是后端设置,不是前端改 axios;npm 用来下载包,node 用来执行文件,不要混;开发阶段用 CORS,不代表上线后可以无脑开放所有源。

上线部署:同源访问 ​

为什么需要:转写稿提出一个关键问题:如果上线后后端仍开启 CORS,攻击者的钓鱼网站也可能来访问服务器,存在风险。因此上线部署时通常关闭 CORS,改为同源访问。

怎么想:让后端 Web 服务既提供数据接口,也返回网页静态资源。这样浏览器访问 http://localhost:3000/index.html,页面里的 AJAX 也请求 http://localhost:3000/api/province,网页所在源和 AJAX 请求源一致,就是同源访问。

怎么做:用 Express 内置的 static 方法暴露前端文件夹,转写稿强调路径用绝对路径更严谨:

js
server.use(express.static(path.join(__dirname, 'public')))

把前端页面放入 public 文件夹,启动后端后访问 http://localhost:3000/index.html。此时即使后端没有开启 CORS,页面内 AJAX 也能正常拿到数据,因为已经同源。

易错点:同源访问主要用于上线环境;本地开发仍常用 CORS。不要再用 Live Server 的 5500 打开页面去请求 3000 接口,否则又变成跨域。static 暴露的是指定文件夹下的资源,路径不要写错。

小结与自测 ​

什么是跨域访问?网页所在源和 AJAX 请求的源(协议、域名、端口号)不完全相同,就发生跨域访问。开发阶段可用 CORS,让后端在响应头中允许跨域;上线部署可把前端静态资源放到后端 Web 服务下,让页面和接口同源。遇到报错时,先看 Console 和 Network,再判断两个源的三元组是否一致。

先理解:为什么会出现跨域问题 ​

为什么需要:前后端分离项目中,前端页面和后端接口常常不在同一个“源”下。浏览器有同源策略,它会限制一个源的文档或脚本与另一个源的资源进行数据交互。转写稿里举了钓鱼网站的例子:攻击者页面里的 AJAX 想把用户填写的信息请求到真正银行服务器,浏览器会用同源策略限制这种跨源请求,从而降低风险。

怎么想:判断是否同源,只看 URL 中的协议、域名、端口号,三者全部一致才是同源;资源路径不参与同源判断。比如页面在 http://localhost:5500,AJAX 请求 http://localhost:3000/api/province,端口不同,就是跨域。转写稿中在浏览器控制台看到类似 Access to XMLHttpRequest ... has been blocked by CORS policy 的报错,就是跨域被浏览器拦截了。

怎么做:先明确“跨域不是请求没发出去,而是浏览器基于同源策略不让前端脚本读取响应”。开发阶段要解决它,让前后端能正常通信。

易错点:不要以为路径不同就一定跨域;真正要看协议、域名、端口。也不要以为换一个接口路径就能绕过同源策略。

CORS 的思路:让服务器声明允许谁来访问 ​

为什么需要:前后端分离时,前端在 localhost:5500 开发,后端在 localhost:3000 提供接口,二者不同源,浏览器会拦截 AJAX 响应,省份列表等数据就拿不到。

怎么想:采用 CORS(跨域资源共享)。它是一种基于 HTTP 头的机制:服务器通过响应头标明,除了自己以外,哪些源可以访问自己的资源。核心响应头是 Access-Control-Allow-Origin。前端仍然正常发起 AJAX 请求,不需要额外操作;关键是后端在响应时加上允许跨域的响应头。

CORS 跨域资源共享机制示意图

怎么做:在 Express 搭建的 Web 服务中引入 cors 中间件,让服务在响应时自动添加相关响应头。笔记中的核心代码是:

js
// 2. 导入 cors 函数
const cors = require('cors')
// 3. 使用 server.use() 给 Web 服务添加插件功能
server.use(cors())

易错点:CORS 主要由后端配置,不是前端在 Axios 里“加一个参数”就完成;cors 是软件包名,不要写成 cars、cse 等同音错词;server.use(cors()) 中 cors 要调用,传入的是调用后的结果。

后端接入 cors 的步骤与调试过程 ​

为什么需要:只理解概念不够,要把后端服务真正开启 CORS,才能验证跨域问题是否解决。

怎么想:按“下载包 → 导入函数 → 注册到 Web 服务 → 启动测试”的顺序做。转写稿中先启动 3000 端口后端,用浏览器地址栏访问接口,确认后端本身没问题;再用 Live Server 在 5500 端口打开前端页面,此时 Axios 请求 3000 端口接口,控制台出现跨域报错。接着给后端加 cors,重新启动服务,再刷新前端页面,报错消失,并拿到省份列表数据。

怎么做:

  1. 在后端项目目录下执行 npm i cors 下载本地软件包;
  2. 在 server.js 中导入:const cors = require('cors');
  3. 通过 server.use(cors()) 给 Web 服务添加插件功能;
  4. 启动后端服务,打开浏览器开发者工具的 Network 面板查看响应头,应能看到 Access-Control-Allow-Origin 字段;再回到前端页面刷新,确认控制台跨域报错消失、数据正常返回。

易错点:下载包要在后端项目目录中执行;改完后端代码要重启服务;调试时先看控制台报错,再看 Network 的响应头,不要只盯着前端代码。CORS 解决的是开发阶段前后端分离的跨域通信问题,前端仍然按正常方式使用 Axios 发请求即可。

本节目标:上线后为什么改用同源访问 ​

为什么需要:开发阶段前后端分离,前端常由 Live Server 在 5500 端口打开,后端接口在 3000 端口,端口不同就会跨域。开发时可以用 CORS 解决,让后端通过响应头允许其他源访问。但项目上线后,如果仍开启 CORS,攻击者的钓鱼网站里也能用 AJAX 请求后端接口,存在安全风险。

怎么想:CORS 适合本地开发阶段;上线部署时关闭 CORS,改为同源访问,让浏览器打开网页的源和 AJAX 请求接口的源完全一致,从源头上避免跨域。

怎么做:让后端 Web 服务既能提供数据接口,也能返回网页静态资源;把前端网页部署到后端服务下,浏览器直接访问后端服务返回的网页,网页里的 AJAX 再请求同一服务下的接口。

易错点:不要把 CORS 当成所有阶段通用方案;上线后不要继续任意源放行;同源判断只看协议、域名、端口,不看资源路径。

同源访问的核心:网页和接口在同一个源 ​

为什么需要:要判断什么时候会跨域,必须先明确“源”的组成。转写稿中反复强调:源由协议、域名、端口号组成;网页所在源与 AJAX 请求的源三者全部一致,才算同源。

怎么想:例如浏览器访问 http://localhost:3000/index.html,页面内 AJAX 请求 http://localhost:3000/api/province,协议、域名、端口都相同,所以是同源访问。若网页仍由 Live Server 的 http://localhost:5500 打开,再请求 http://localhost:3000/api/province,端口不同,仍然跨域。

怎么做:上线时把前端网页和后端接口放到同一个 Web 服务、同一个源下访问。后端既返回网页资源,又提供数据接口。

易错点:不要用资源路径判断同源;/api/province 和 /index.html 路径不同不影响同源判断;只要协议、域名、端口有一个不同,就是跨域。

同源访问示意图

让 Express 后端同时返回网页资源 ​

为什么需要:同源访问要求网页也由后端 Web 服务返回。若后端只提供接口,不返回网页,浏览器仍可能从别的源打开页面,跨域问题依旧存在。

怎么想:Express 快速搭建的 Web 服务可以借助内置的 static 方法,把指定文件夹暴露为静态资源目录。浏览器通过“域名 + 端口 + 资源路径”就能访问该目录下的 HTML、JS 等资源。

怎么做:把前端写好的网页放进 public 文件夹,在 server.js 中暴露该目录:

js
// 暴露指定的文件夹,让前端可以直接拼接路径和资源名字来访问
server.use(express.static(path.join(__dirname, 'public')))

这样后端服务既能提供接口,也能返回 public 下的网页资源。转写稿还提醒:代码中访问其他文件时,用 path.join(__dirname, 'public') 写绝对路径更严谨。

易错点:不要只写相对路径 'public';暴露的是 public 目录本身,访问其中 index.html 时 URL 通常是 /index.html,不要误以为一定要写 /public/index.html;路径拼接不要漏掉 __dirname。

调试与验证:关闭 CORS 后仍能拿到数据 ​

为什么需要:同源访问不是只改概念,必须验证关闭 CORS 后页面和接口仍能正常通信。

怎么想:启动后端服务,用浏览器直接访问后端返回的网页;打开 F12,在 Network/Console 中观察 AJAX 请求是否成功。若网页源和接口源一致,即使没有开启 CORS,也能拿到数据。

怎么做:在终端执行 node server.js 启动后端;浏览器访问后端服务返回的 public/index.html;页面内 AJAX 请求同源接口,例如 /api/province;F12 刷新后查看响应数据。

易错点:如果仍用 Live Server 的 5500 打开网页,再请求 3000 接口,仍会跨域;若控制台报 Access to XMLHttpRequest 一类错误,先检查网页是不是从后端同源地址打开,以及后端服务是否已启动。

小结与自测 ​

  1. CORS 只适用于什么阶段的项目?
    答案:本地开发阶段项目。

  2. 项目上线,如何解决跨域问题?
    答案:把前端项目和后端项目部署到同一个源下访问,采用同源访问方式;上线时关闭 CORS,让后端 Web 服务既提供数据接口,也返回网页资源,从而保证接口不被非同源访问。

ECMAScript 标准模块语法:默认导出/导入与按需导出/导入 ​

为什么需要:项目拆成多个文件后,需要把变量、函数、对象从一个文件交给另一个文件使用;ES Module 提供了标准写法,避免把所有代码堆在一个文件里。

怎么想:先区分“只导出一个主功能”和“导出多个小功能”。默认导出适合一个模块一个主入口;按需导出适合工具函数集合。导入时名字要与导出方式匹配。

怎么做:默认导出用 export default,导入时可自定义名字;按需导出用 export,导入时用 { } 且名字对应。转写稿没有展开具体语法,这里只保留标准用法,不额外扩展未讲内容。

易错点:默认导入不要加 { };按需导入不要漏花括号;同一个模块可以既有默认导出又有按需导出,但导入写法要分开。不要把 CommonJS 的 require/module.exports 与 ES Module 混用在同一套写法里。

加载包文件夹时,加载的是什么文件 ​

为什么需要:require('包名') 或导入包文件夹时,Node 需要知道入口文件是谁,否则不知道执行哪个文件。

怎么想:包文件夹不是“随便找第一个 js”。Node 会先看该文件夹下的 package.json,读取 main 字段指向的入口文件;如果没有明确入口,再按默认规则找 index.js 等。转写稿未涉及此细节,这里按通用规则说明,不编造具体项目文件。

怎么做:排查时先打开包目录看 package.json 的 main;再看入口文件是否存在。自己写包时,应在 package.json 中写清入口。

易错点:不要以为文件夹名就是文件名;不要忽略 package.json;入口路径写错时,报错往往不是“语法错”,而是“找不到模块”。

npm 软件包管理器相关命令和作用 ​

为什么需要:项目依赖第三方包时,需要下载、记录、删除和运行脚本;npm 负责管理这些依赖。

怎么想:先初始化项目,再安装依赖;安装后会在项目下生成 node_modules,并在 package.json 中记录依赖。转写稿中明确出现的是下载 CORS 包:在项目文件夹终端执行 npm install cors,下载后 node_modules 中出现 cors 及其依赖。

怎么做:常用命令包括 npm init 初始化、npm install 包名 安装、npm install 按 package.json 安装全部依赖、npm uninstall 包名 卸载、npm run 脚本名 执行脚本。执行 JS 文件用 node 文件名,不是 npm;转写稿中“n moon”应纠正为 node。

易错点:npm 是包管理器,不是执行 JS 文件的命令;安装包要在项目目录终端执行;不要提交 node_modules,应提交 package.json 和锁文件。

浏览器同源策略:协议、域名、端口必须一致 ​

为什么需要:防止来源不明的网页脚本随意读取另一个源的敏感数据。转写稿用钓鱼网站举例:钓鱼页面把用户填写的转账信息通过 Ajax 发往真正银行服务器,如果没有同源策略限制,风险很大。

怎么想:判断同源只看三件事:协议、域名、端口。路径、查询参数不参与同源判断。http://localhost:5500 与 http://localhost:3000 端口不同,不同源;http://icbcbc.com 与 http://icbc.com.cn 域名不同,也不同源。

怎么做:写请求前先比较网页所在源和目标接口源。三个都一致才是同源;任意一个不同就是跨域访问。

易错点:不要看资源路径判断同源;http 默认端口 80,https 默认 443;端口不同也算跨域。转写稿中的“圆”应统一纠正为“源”。

跨域问题与 CORS 解决方案 ​

为什么需要:前后端分离项目中,前端常在 http://localhost:5500 由 Live Server 打开,后端接口在 http://localhost:3000,两者不同源,浏览器会限制 Ajax 响应。转写稿中控制台出现类似 Access to XMLHttpRequest ... 的报错,就是跨域被拦截。

怎么想:跨域是浏览器同源策略导致的;解决思路之一是让后端明确允许某些源访问,即 CORS(跨域资源共享)。它基于 HTTP 响应头机制,前端正常发 Ajax,后端设置响应头。

怎么做:后端安装并引入 cors 包,把 CORS 作为 Express 服务的插件使用。转写稿中的核心做法是:

js
const cors = require('cors');
server.use(cors());

这样后端响应中会带上允许跨域访问的响应头字段。转写稿提到字段名为 Access-Control-Allow-Origin,作用是标记允许其他源访问服务器资源。前端不需要额外操作,仍用 axios/XHR 正常请求和接收响应。

易错点:CORS 主要在后端做,不是前端加个参数就行;开发阶段用 CORS 方便,但不能理解成“永久放开”;转写稿中的“cars/cos/cse”应纠正为 CORS。

上线部署:用同源访问替代开发期 CORS ​

为什么需要:转写稿提出一个关键问题:如果上线后后端仍开启 CORS,攻击者的钓鱼网站也可能请求接口。因此开发阶段可用 CORS,上线部署面对互联网时应关闭 CORS,改用同源访问。

怎么想:让后端 Web 服务既提供数据接口,也返回前端静态网页资源。这样网页所在源和 Ajax 请求接口源一致,就不会触发跨域限制,同时接口不再允许非同源访问,更安全。

怎么做:把前端打包后的网页放进后端可暴露的 public 文件夹,用 Express 的 static 方法暴露该文件夹。转写稿中的核心做法是:

js
server.use(express.static(path.join(__dirname, 'public')));

启动后端后,通过 http://localhost:3000 访问 public 下的页面资源;页面里的 Ajax 也请求同一端口下的接口,即同源访问。转写稿中调试时先访问接口确认 3000 服务正常,再用 Live Server 的 5500 打开前端复现跨域报错,最后用 CORS 解决;上线方案则改为同源。

易错点:express.static 暴露的是文件夹,访问时要拼资源路径和文件名;转写稿强调路径用绝对路径更严谨,path.join(__dirname, 'public') 中的 __dirname 不要写错;同源访问主要适用于上线部署,本地开发仍可能使用 CORS。

本节的任务:完成今日作业 ​

本节是今日作业(必完成)。具体题目、提交方式与评分要求,请以配套文件夹里 <作业> 目录下的 md 文件为准,这里不重复抄写。

作业会用到的知识就是下面四块:浏览器同源策略、跨域问题、CORS 跨域资源共享、同源访问。下面按“为什么需要 → 怎么想 → 怎么做 → 易错点”的顺序把这一节串一遍,做完再看作业要求,基本就能直接下手。

为什么浏览器要有同源策略 ​

为什么需要:假设你不小心打开了一个来源不明的钓鱼网站,比如 http://icbcbc.com,它把页面做得和真银行一模一样。你输入金额、收款信息和密码点提交,这个页面里的 Ajax 就会把你填的真实信息请求到真正的银行服务器。如果浏览器不管,你的钱就被转给攻击者了。

怎么想:MDN 的定义是——同源策略限制一个源的文档,或它加载的 JS 脚本,能否与另一个源的资源进行交互。它的作用就是阻挡恶意文档、减少可能被攻击的面。

怎么做:不需要你写代码开启,它是浏览器内置的行为,你只要理解它限制了什么:Ajax 只能向同源的 URL 发起请求。

易错点:同源策略是浏览器端的安全机制,不是服务器端的;它限制的是“读响应”,不是“请求发不出去”。

怎么判断“同源”:协议 + 域名 + 端口 ​

为什么需要:要判断一次请求会不会被拦,先得会判断两个 URL 是不是同源。

怎么想:源(origin)由 URL 中的三部分组成——协议、域名、端口号。三者全部一致才是同源,有一个不同就是跨域。

怎么做:拿钓鱼网站的例子对比:

  • 网页所在源:http://icbcbc.com,协议 http、域名 icbcbc.com、http 默认端口 80
  • Ajax 请求源:http://icbc.com.cn,协议 http、域名 icbc.com.cn、端口 80

协议和端口相同,但域名不同,所以不是同源,浏览器会限制这次 Ajax 的请求响应。

易错点:判断同源不看资源路径,因为源里只有协议、域名、端口三样东西,/api/province 这类路径不参与判断。

跨域问题是怎么产生的 ​

为什么需要:知道同源规则后,就要能看懂实际开发中那条报错。

怎么想:网页加载时所在的源,和网页里内嵌的 Ajax 要请求的源,协议、域名、端口只要有一个不同,就是跨域访问,这次请求和响应会失败。

怎么做:用 Live Server 插件打开前端页面,它启动的服务是 http://localhost:5500;页面里用 axios 请求 http://localhost:3000/api/province。两个源端口不同(5500 与 3000),于是 F12 控制台报出 Access to XMLHttpRequest at ... has been blocked by CORS policy 这类错误,意思就是 XHR 连接出了问题。

易错点:看到这条报错先比三个要素,别急着改前端代码;跨域不是“请求没发出去”,而是响应被浏览器拦下了。

解决方案一:CORS 跨域资源共享(开发阶段用) ​

为什么需要:现在很多项目是前后端分离的,前端代码和后端代码不在同一个源下,但数据通信还必须正常进行,所以必须解决跨域。

怎么想:CORS 是一种基于 HTTP 头的机制。服务器在响应头里加一个固定字段 Access-Control-Allow-Origin,用来标记“除了跟我同源之外的其他源,也可以访问我服务器上的资源”。前端不需要做额外操作。

怎么做:三步都在后端。

  1. 在后端项目目录下装包(用包管理器 npm,不是用 node 执行文件):
bash
npm i cors
  1. 在 server.js 中引入并注册:
js
const cors = require('cors')
const express = require('express')
const server = express()

// 给当前 web 服务添加插件功能,下面所有接口都开启 CORS
server.use(cors())

server.get('/api/province', (req, res) => {
  // 这里返回之前写好的省份列表数据
  res.send({ code: 200, msg: '获取省份列表成功', data: provinceList })
})

server.listen(3000, () => {
  console.log('http://localhost:3000')
})
  1. 用 nodemon server.js 启动服务,浏览器访问 http://localhost:3000/api/province,F12 → Network 查看响应头,能看到 Access-Control-Allow-Origin: *,说明允许任意源访问。

前端这边照常写就行:

js
axios.get('http://localhost:3000/api/province')
  .then(result => {
    console.log(result.data)
  })
  .catch(error => {
    console.log(error)
  })

axios 调用后原地返回的是 Promise 对象,所以能点 .then 拿成功结果、点 .catch 拿失败结果;axios 内部已把 JSON 字符串转成 JS 对象放进 data 里。

易错点:CORS 是后端开启的,前端改代码解决不了;装包命令是 npm i cors;server.use(cors()) 要写在接口之前,才能对下面所有接口生效。

解决方案二:同源访问(上线部署用) ​

为什么需要:如果上线后还开着 CORS,等于允许任意源(包括攻击者的钓鱼网站)来访问你的接口,很危险。所以开发阶段用 CORS,项目上线面对整个互联网时要关掉它,改用同源访问。

怎么想:让后端这个 web 服务既提供数据接口,也返回前端网页的静态资源。浏览器直接访问 http://localhost:3000/index.html,页面里 Ajax 请求 http://localhost:3000/api/province,两个源完全相同,天然同源,不存在跨域。

怎么做:Express 内置了 static 方法,把前端打包好的文件夹暴露出去。代码里访问其他文件建议写绝对路径,更严谨:

js
const path = require('path')
const express = require('express')
const server = express()

// 暴露 public 文件夹,前端可用 域名+端口+路径+文件名 访问其中资源
server.use(express.static(path.join(__dirname, 'public')))

server.listen(3000)

启动后浏览器访问 http://localhost:3000/index.html,就能拿到 public 下的前端页面;页面里 Ajax 请求同源的 /api/province 也能正常拿到数据,而且此时后端并没有开启 CORS。

易错点:express.static 传的是文件夹路径,用 path.join(__dirname, 'public') 拼绝对路径;上线用同源、开发用 CORS,两者不要混着用。

本节小结与作业提示 ​

  • 同源策略:限制一个源与另一个源的数据交互,具体限制的是 Ajax 只能访问同源的 URL。
  • 同源判断:网页所在源与 Ajax 请求源的协议、域名、端口全部一致,不看路径。
  • 跨域:三者有一个不同就产生跨域,控制台会报 Access to XMLHttpRequest ... blocked by CORS policy。
  • CORS:后端装 cors 包并 server.use(cors()),响应头出现 Access-Control-Allow-Origin,用于开发阶段的前后端分离项目。
  • 同源访问:后端用 express.static 同时托管页面与接口,用于上线部署,更安全。

带着这五点去看 <作业> 目录下 md 文件的具体要求,逐条对照完成并提交即可。

本节要解决什么问题 ​

本节从浏览器同源策略讲起,解释什么是跨域、为什么会出现跨域报错,并给出两种处理思路:开发阶段用 CORS 跨域资源共享,上线部署时用同源访问。转写稿里“圆”应统一纠正为“源”,“HX/AJX”指 AJAX,“X优/XU”指 axios,“cars/cos/cse”指 CORS,“le server/li server”指 Live Server 插件。原笔记还列了 package.json、开源许可、npm 命令、export/import 等参考文档,本文保留链接,作为扩展查阅资料。

同源策略:先弄清“源”是什么 ​

为什么需要:浏览器要阻止恶意网页随意读取另一个源的敏感数据。转写稿举例:钓鱼网站伪装成银行页面,如果它能用 AJAX 把用户填写的转账信息发到真正银行服务器并读取结果,就很危险。同源策略限制一个源的文档或脚本与另一个源的资源交互,从而减少攻击面。

怎么想:判断同源只看 URL 中的协议、域名、端口号,三者必须全部一致;资源路径不参与判断。http://localhost:5500 与 http://localhost:3000 端口不同,不同源;http://icbcbc.com 与 http://icbc.com.cn 域名不同,也不同源。

怎么做:遇到 AJAX 请求前,先写出“网页所在源”和“请求目标源”,逐项对比协议、域名、端口。转写稿中 Live Server 打开网页是 http://localhost:5500,请求后端是 http://localhost:3000/api/province,端口不同,所以跨域。

易错点:不要用资源路径判断跨域;不要以为域名相似就是同源;协议从 HTTP 换成 HTTPS、端口从 3000 换成 5500,都可能导致不同源。

跨域问题:报错是怎么出现的 ​

为什么需要:前后端分离项目中,前端页面常在一个源下开发,后端接口在另一个源下运行。浏览器仍执行同源策略,所以 AJAX 请求会失败。

怎么想:先启动后端服务,确认接口本身可用;再用 Live Server 启动前端页面;最后打开 F12 看控制台和 Network。转写稿的调试过程是:在 3000 端口启动后端,浏览器直接访问 http://localhost:3000/api/province 能拿到数据;但从前端 5500 页面里用 axios 请求 3000 接口时,控制台出现 Access to XMLHttpRequest 相关跨域错误。

怎么做:后端启动命令可用:

bash
node server.js
# 或使用 nodemon 监听文件变化
nodemon server.js

前端请求核心代码:

js
axios.get('http://localhost:3000/api/province')
  .then(res => {
    console.log(res.data)
  })
  .catch(err => {
    console.log(err)
  })

易错点:接口在浏览器地址栏能打开,不代表页面里的 AJAX 一定能跨域读取;跨域失败时先看控制台报错和响应头,不要只改前端请求路径。

CORS:开发阶段让后端允许跨域 ​

为什么需要:前后端分离开发时,前端和后端不在同一个源,但数据通信必须正常进行。CORS 是跨域资源共享机制,基于 HTTP 响应头工作。

怎么想:跨域限制由浏览器执行,解决方案要落到服务器响应上。后端设置固定响应头字段 Access-Control-Allow-Origin,告诉浏览器允许哪些源访问资源。转写稿中使用 cors 本地软件包,把它挂到 express 的 web 服务对象上。

怎么做:在 13 项目后端目录安装依赖:

bash
npm i cors

后端核心代码:

js
const cors = require('cors')
// server 是 express 搭建的 web 服务对象
server.use(cors())

启动后端后,在 Network 的响应头中可看到 Access-Control-Allow-Origin。前端无需额外操作,仍正常使用 axios 发请求、接收响应。转写稿中刷新 5500 页面后,控制台跨域报错消失,res.data 中拿到省份列表数据。

易错点:CORS 主要在后端配置,不是前端加个请求头就解决;cors() 挂载位置要在接口响应之前;上线后不要长期无条件开放跨域,否则钓鱼网站也可能访问接口。

同源访问:上线部署更安全的做法 ​

为什么需要:转写稿提到,如果上线后后端仍开启 CORS,攻击者页面也可能请求接口。上线部署时更推荐让前端网页和后端接口在同一个 web 服务下,变成同源访问。

怎么想:让后端服务既提供数据接口,也返回前端静态资源。浏览器访问 http://localhost:3000/index.html,页面里的 AJAX 再请求 http://localhost:3000/api/province,协议、域名、端口一致,就是同源访问。

怎么做:把前端页面放入 public 文件夹,后端用 express 的 static 暴露静态资源。转写稿强调路径要写绝对路径更严谨:

js
const path = require('path')
server.use(express.static(path.join(__dirname, 'public')))

启动后端后访问 http://localhost:3000/index.html,页面能加载,AJAX 也能拿到数据,且不需要开启 CORS。

易错点:不要手写容易出错的相对路径读取文件;上线同源方案不等于开发阶段也关闭 CORS,开发时仍常配合 CORS;静态资源路径和接口路径要区分清楚。

原笔记参考文档保留 ​

  1. package.json - 更多属性含义
  2. 开源许可 - 更多资料
  3. 开源许可 - 更多资料2
  4. 更多 npm 命令
  5. export导出-更多学习
  6. import导入-更多学习
  7. 同源策略
  8. 跨域资源共享CORS

这些文档适合按需查阅:package.json 字段、开源许可协议、npm 命令、ES 模块 import/export 都不要凭记忆写,遇到项目配置或合规问题以官方文档和项目实际为准。

课件图示 ​

课件第33页:目标:开发环境用cors,上线部署关闭cors,并采用同源访问方式 做法:让后端

课件第30页:目标:前后端分离的项目,前端和后端不在一个源,还要保证数据通信 解决:采用COR

课件第31页:步骤: 1. 下载cors 本地软件包 2. 导入cors 函数 3. 使用se

课件第32页:总结 总结 1. 为什么要解决跨域问题?  因为前后端分离的项目,不在同一个源

课件第28页:跨域:从一个源的文档/脚本,加载另一个源的资源就产生了跨域 例如:网页所在源和A

课件第10页:包:将模块,代码,其他资料聚合成一个文件夹 包分类:  项目包:主要用于编写项

课件第15页:问题:项目中不包含node_modules,能否正常运行? 答案:不能,缺少依赖

课件第25页:同源策略: 例如:被钓鱼网站收集信息,使用AJAX 发起恶意请求,传递转账信息给

代码示例 ​

示例 1:复现跨域问题(前端 Live Server 5500 → 后端 3000) ​

后端 server.js(端口 3000)

js
// 后端:基于 express 提供省份列表接口
const express = require('express')
const server = express()

server.get('/api/province', (req, res) => {
  res.send({
    code: 200,
    msg: '获取省份列表成功',
    data: ['北京市', '上海市', '广东省']
  })
})

server.listen(3000, () => {
  console.log('Web 服务已启动:http://localhost:3000')
})

前端 index.html(用 Live Server 打开,端口 5500)

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>跨域演示</title>
  <!-- 引入 axios -->
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
  <script>
    // 目标:请求本机 Web 服务提供的省份列表数据
    axios({
      url: 'http://localhost:3000/api/province',
      // method: 'get'   // axios 默认就是 get,可省略
    }).then(res => {
      console.log(res)
    }).catch(err => {
      console.log(err)
    })
  </script>
</body>
</html>

要点解释 / 易错点

  • 判断是否跨域只看 协议、域名、端口 三者,不看资源路径(/api/province 不影响判断)。
  • 本例:网页源 http://localhost:5500,请求源 http://localhost:3000 → 端口不同 → 跨域。
  • 控制台会报 Access to XMLHttpRequest at ... from origin ... has been blocked by CORS policy,then 拿不到数据,走到 catch。
  • 易错点:浏览器地址栏直接访问 http://localhost:3000/api/province 是能拿到数据的(不受同源策略限制),只有网页里的 AJAX 才会被拦截,不要误以为后端接口写错了。
  • 易错点:用 file:// 直接双击打开 html 也会跨域,请用 Live Server 起 5500 端口。

示例 2:解决方案 1 —— CORS(开发阶段) ​

js
// 1. 先下载 cors 本地软件包:npm i cors
const express = require('express')
// 2. 导入 cors 函数
const cors = require('cors')

const server = express()

// 3. 使用 server.use() 给 Web 服务添加插件功能
server.use(cors())

server.get('/api/province', (req, res) => {
  res.send({
    code: 200,
    msg: '获取省份列表成功',
    data: ['北京市', '上海市', '广东省']
  })
})

server.listen(3000, () => {
  console.log('Web 服务已启动:http://localhost:3000')
})

前端代码完全不用改,仍是示例 1 的 axios({ url: 'http://localhost:3000/api/province' })。

要点解释 / 易错点

  • CORS = 跨域资源共享,基于 HTTP 响应头:Access-Control-Allow-Origin,允许除自己以外的源访问本服务器资源。
  • 关键行 server.use(cors()):server.use() 是给整个 Web 服务注册插件,写在所有路由之前,这样下面所有接口的响应都会带上该响应头。
  • 验证方法:F12 → Network → 刷新请求 → 查看「响应头」中是否有 Access-Control-Allow-Origin: *。
  • 易错点:npm i cors 必须在后端项目文件夹(有 package.json 的目录)下执行,否则会装到别处导致 require('cors') 报找不到模块。
  • 易错点:CORS 只适用于本地开发阶段;上线后开启 * 意味着任何钓鱼网站都能请求你的接口,不安全。

示例 3:解决方案 2 —— 同源访问(上线部署阶段) ​

后端 server.js(同时提供接口 + 网页资源)

js
const express = require('express')
const path = require('path')
const server = express()

// 暴露指定的文件夹,让前端可以直接拼接路径和资源名字来访问
server.use(express.static(path.join(__dirname, 'public')))

// 数据接口(注意:不再开启 cors)
server.get('/api/province', (req, res) => {
  res.send({
    code: 200,
    msg: '获取省份列表成功',
    data: ['北京市', '上海市', '广东省']
  })
})

server.listen(3000, () => {
  console.log('Web 服务已启动:http://localhost:3000')
})

public/index.html(前端网页,随后端一起部署)

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>同源访问演示</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
  <script>
    // 网页所在源:http://localhost:3000
    // 请求的源:  http://localhost:3000   → 同源,浏览器放行
    axios({
      url: 'http://localhost:3000/api/province'
    }).then(res => {
      console.log(res)
    }).catch(err => {
      console.log(err)
    })
  </script>
</body>
</html>

要点解释 / 易错点

  • 做法:让后端 Web 服务既提供数据接口,又返回网页静态资源,前端和后端部署到同一个源下。
  • 关键行 server.use(express.static(path.join(__dirname, 'public'))):把 public 文件夹作为静态资源目录暴露出去,浏览器用 http://localhost:3000/index.html 即可访问该网页。
  • 用绝对路径 path.join(__dirname, 'public') 更严谨(__dirname 是当前文件所在文件夹的绝对路径)。
  • 验证:此时没有 Access-Control-Allow-Origin 响应头,控制台也不报错,数据照样拿到 —— 因为网页源与请求源完全一致。
  • 易错点:express.static 挂载后访问路径是 /index.html,不带 public 前缀,写成 /public/index.html 会 404。
  • 易错点:前端页面里的请求地址必须与网页所在源一致(同源),如果还写着 5500 或别的端口,照样跨域。
  • 易错点:上线时记得关闭 CORS,只保留同源访问,接口才不会被非同源网站调用。

常见错误 ​

  1. 现象:控制台报 Access to XMLHttpRequest at 'http://localhost:3000/api/province' from origin 'http://localhost:5500' has been blocked by CORS policy,但反复检查 axios 的 url、method、参数都“没错”。 原因:把跨域当成前端代码 bug,在请求头、参数、axios 配置上反复改。 修法:跨域只看协议、域名、端口三者是否一致,与请求参数无关;改前端配置无效,必须去后端处理。

  2. 现象:以为 /api/province 和 /api/city 属于不同源,或以为把请求改成相对路径就能绕过。 原因:把资源路径、查询串算进了同源判断。 修法:同源只比对协议、域名、端口,路径、参数、hash 一律不参与判断。

  3. 现象:npm i cors 提示成功、代码里也写了 require('cors'),浏览器仍报跨域;或报 Cannot find module 'cors'。 原因:终端不在 package.json 所在目录就安装了,包被装到了别处;或只 require 了却没挂载到服务上。 修法:切到项目根目录安装,确认 node_modules/cors 存在,且代码里有 server.use(cors()) 这一句。

  4. 现象:加了 cors 后,一部分接口正常,另一部分仍报跨域。 原因:server.use(cors()) 写在了部分路由注册之后,中间件只对其后注册的路由生效。 修法:把 server.use(cors()) 提到所有路由之前,紧跟 server 对象创建之后。

  5. 现象:后端代码明明改了,刷新浏览器还是同一条报错。 原因:Node 进程没重启,跑的还是旧代码(Node 不会热更新)。 修法:终端 Ctrl+C 停掉,重新 node server.js,再刷新页面。

  6. 现象:双击 html 用 file:// 打开,请求被拦,报 origin 为 null;后端明明开了 cors 也不通。 原因:file:// 没有 http 源,不属于同源模型,无法参与 CORS 协商。 修法:用 Live Server 之类起一个 http 服务再测,保证页面有真实的协议、域名、端口。

  7. 现象:项目上线后,任意第三方站点都能调用自己的接口。 原因:生产环境沿用了开发期的 server.use(cors())(等价于 Access-Control-Allow-Origin: *)。 修法:上线时关掉 cors,改为同源部署——server.use(express.static(path.join(__dirname, 'public'))),让页面和接口同一个源。

练习 ​

  1. 同源判断:以下每组是否同源?只写判断依据(协议 / 域名 / 端口)。

    • a) 页面 http://localhost:5500/index.html 请求 http://localhost:5500/api/province
    • b) 页面 http://localhost:5500 请求 http://localhost:3000/api/province
    • c) 页面 https://api.shop.com 请求 http://api.shop.com/api
    • d) 页面 http://shop.com 请求 http://shop.com:80/api
    • e) 页面 http://shop.com 请求 http://www.shop.com/api
  2. 排错:下面后端代码启动后,前端 5500 端口访问仍报跨域,找出两处问题并改正。

    js
    const express = require('express')
    const server = express()
    server.get('/api/province', (req, res) => { res.send(['北京', '上海']) })
    const cors = require('cors')
    server.use(cors())
    server.listen(3000, () => console.log('running'))

    (提示:一处与中间件顺序有关,一处与运行状态有关。)

  3. 动手:给一个已写好的 express 服务开启跨域资源共享,并写出验证步骤:打开 F12 后要在哪个面板、看哪个响应头字段,才算开启成功。

  4. 场景设计:项目要上线,要求接口不被非同源站点调用。请写出让同一个 web 服务既返回网页又提供接口的关键代码,并说明此时前端页面里的请求地址应该怎么写最稳妥。

  5. 对比说明:分别说出开发阶段和生产阶段采用的跨域方案,并解释“为什么不能把开发阶段那套直接带上线”。

原视频对照 ​

#视频时长
1浏览器的同源策略05:28
2跨域问题11:08
3跨域问题-解决方案1-CORS12:26
4跨域问题-解决方案2-同源访问08:20

小结 ​

  1. 同源判断只有三个维度——协议、域名、端口,路径不参与;三者全同才算同源。
  2. 跨域报错是浏览器的拦截行为,请求可能已经发出、服务端也可能已响应,所以它不是“前端写错了”。
  3. CORS 的决定权在服务端响应头,前端唯一要做的就是正常发请求,不要试图用前端手段“绕过”。
  4. cors 中间件必须挂在所有路由之前,否则会出现“部分接口通、部分接口不通”的诡异现象。
  5. Node 改完代码必须重启进程,否则一切验证结论都不可信。
  6. 开发阶段用 CORS,生产阶段用同源部署(后端同时提供接口和静态页面),两套方案各管一段,不能混用。
  7. 生产环境保留 Access-Control-Allow-Origin: * 等于把接口开放给全网,是安全问题而不是配置问题。
  8. 同源部署后,前端请求写相对路径(如 /api/province)即可天然同源,不要把域名和端口写死。