网易首页 > 网易号 > 正文 申请入驻

前端如何解决跨域问题?

0
分享至

作者:五月君 来源:Nodejs技术栈

“前端如何解决跨域问题?” 这个是前段在知乎看到的一个提问,这几乎是做前端都会遇到的一个问题,产生的情况可能会很多,解决一个问题还是要先了解下为什么会产生这样问题,学习最好的方法就是结合一些实际的案例来学习,理解和掌握也会更加的深刻,本文结合 Node.js 写一些 Demo 看一下跨域问题及解决办法,最好是自己看完也能够动手操作下~

Cross-origin Resource Sharing 中文名称 “跨域资源共享” 简称 “CORS”,它突破了一个请求在浏览器发出只能在同源的情况下向服务器获取数据的限制。

本文会先从一个示例开始,分析是浏览器还是服务器的限制,之后讲解什么时候会产生预检请求,在整个过程中,也会讲解一下解决该问题的实现方法,文末会再总结如何使用 Node.js 中的 cors 模块和 Nginx 反向代理来解决跨域问题。

文中使用 Node.js 做一些 Demo 的演示,每一小节之后也会给予代码的 Demo 地址。

浏览器还是服务器的限制

先思考下,CORS 是浏览器端还是服务器端的限制?为了更好地说明这个问题,从一段示例开始。

从一段示例开始

index.html

client.js

创建 client.js 用来加载上面 index.html。设置端口为 3010。

const http = require('http');
const fs = require('fs');
const PORT = 3010;
http.createServer((req, res) => {
fs.createReadStream('index.html').pipe(res);
}).listen(PORT);

server.js

创建 server.js 开启一个服务,根据不同的请求返回不同的响应。设置端口为 3011。

const http = require('http');
const PORT = 3011;
http.createServer((req, res) => {
const url = req.url;
console.log('request url: ', url);
if (url === '/api/data') {
return res.end('ok!');
if (url === '/script') {
return res.end('console.log("hello world!");');
}).listen(PORT);
console.log('Server listening on port ', PORT);
测试分析原因

运行上面的 client.js、server.js 浏览器输入 http://127.0.0.1:3010 在 Chrome 浏览器中打开 Network 项查看请求信息,如下所示:

左侧是使用 fetch 请求的 127.0.0.1:3011/api/data 接口,在请求头里可以看到有 Origin 字段,显示了我们当前的请求信息。另外还有三个 Sec-Fetch-* 开头的字段,这是一个新的草案 Fetch Metadata Request Headers[1]。

其中 Sec-Fetch-Mode 表示请求的模式,通过左右两侧结果对比也可以看出左侧是跨域的。Sec-Fetch-Site 表示的是这个请求是同源还是跨域,由于我们这两个请求都是由 3010 端口发出去请求 3011 端口,是不符合同源策略的。

看下浏览器 Console 下的日志信息,根据提示得知原因是从 “http://127.0.0.1:3010” 访问 “http://127.0.0.1:3011/api/data” 被 CORS 策略阻止了,没有 “Access-Control-Allow-Origin” 标头。

在看下服务端的日志,因为请求 3011 服务,所以就看下 3011 服务的日志信息:

Server listening on port 3011
request url: /script
request url: /api/data

在服务端是有收到请求信息的,说明服务端是正常工作的。

我们也可以在终端通过 curl 命令测试下,在终端脱离浏览器环境也是可以正常请求的。

$ curl http://127.0.0.1:3011/api/data
ok!

本节代码示例:

github.com/qufei1993/http-protocol/tree/master/example/cors/01

总结回答最开始提出的问题

浏览器限制了从脚本内发起的跨源 HTTP 请求,例如 XMLHttpRequest 和我们本示例中使用的 Fetch API 都是遵循的同源策略。

当一个请求在浏览器端发送出去后,服务端是会收到的并且也会处理和响应,只不过浏览器在解析这个请求的响应之后,发现不属于浏览器的同源策略(地址里面的协议、域名和端口号均相同)也没有包含正确的 CORS 响应头,返回结果被浏览器给拦截了。

预检请求

预检请求是在发送实际的请求之前,客户端会先发送一个 OPTIONS 方法的请求向服务器确认,如果通过之后,浏览器才会发起真正的请求,这样可以避免跨域请求对服务器的用户数据造成影响。

看到这里你可能有疑问为什么上面的示例没有预检请求?因为 CORS 将请求分为了两类:简单请求和非简单请求。我们上面的情况属于简单请求,所以也就没有了预检请求。

让我们继续在看下简单请求和非简单请求是如何定义的。

预检请求定义

根据 MDN 的文档定义,请求方法为:GET、POST、HEAD,请求头 Content-Type 为:text/plain、multipart/form-data、application/x-www-form-urlencoded 的就属于 “简单请求” 不会触发 CORS 预检请求。

例如,如果请求头的 Content-Type 为 application/json 就会触发 CORS 预检请求,这里也会称为 “非简单请求”。

“MDN 文档 developer.mozilla.org/en-US/docs/Web/HTTP/CORS 简单请求”[2] 有更多关于简单请求的字段定义。

预检请求示例

通过一个示例学习下预检请求。

设置客户端

为 index.html 里的 fetch 方法增加一些设置,设置请求的方法为 PUT,请求头增加一个自定义字段 Test-Cors。

上述代码在浏览器执行时会发现是一个非简单请求,就会先执行一个预检请求,Request Headers 会有如下信息:

OPTIONS /api/data HTTP/1.1
Host: 127.0.0.1:3011
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type,test-cors
Origin: http://127.0.0.1:3010
Sec-Fetch-Mode: cors

可以看到有一个 OPTIONS 是预检请求使用的方法,该方法是在 HTTP/1.1 协议中所定义的,还有一个重要的字段 Origin 表示请求来自哪个源,服务端则可以根据这个字段判断是否是合法的请求源,例如 Websocket 中因为没有了同源策略限制,服务端可以根据这个字段来判断。

Access-Control-Request-Method 告诉服务器,实际请求将使用 PUT 方法。

Access-Control-Request-Headers 告诉服务器,实际请求将使用两个头部字段 content-type,test-cors。这里如果 content-type 指定的为简单请求中的几个值,Access-Control-Request-Headers 在告诉服务器时,实际请求将只有 test-cors 这一个头部字段。

设置服务端

上面讲解了客户端的设置,同样的要使请求能够正常响应,还需服务端的支持。

修改我们的 server.js 重点是设置 Response Headers 代码如下所示:

res.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:3010',
'Access-Control-Allow-Headers': 'Test-CORS, Content-Type',
'Access-Control-Allow-Methods': 'PUT,DELETE',
'Access-Control-Max-Age': 86400

为什么是以上配置?首先预检请求时,浏览器给了服务器几个重要的信息 Origin、Method 为 PUT、Headers 为 content-type,test-cors 服务端在收到之后,也要做些设置,给予回应。

Access-Control-Allow-Origin 表示 “http://127.0.0.1:3010” 这个请求源是可以访问的,该字段也可以设置为 “*” 表示允许任意跨源请求。

Access-Control-Allow-Methods 表示服务器允许客户端使用 PUT、DELETE 方法发起请求,可以一次设置多个,表示服务器所支持的所有跨域方法,而不单是当前请求那个方法,这样好处是为了避免多次预检请求。

Access-Control-Allow-Headers 表示服务器允许请求中携带 Test-CORS、Content-Type 字段,也可以设置多个。

Access-Control-Max-Age 表示该响应的有效期,单位为秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。还有一点需要注意,该值要小于浏览器自身维护的最大有效时间,否则是无效的。

看下增加了预检请求的效果,第一次先发出了 OPTIONS 请求,并且在请求头设置了本次请求的方法和 Headers 信息,服务端在 Response 也做了回应,在 OPTIONS 成功之后,浏览器紧跟着才发起了我们本次需要的真实请求,如图右侧所示 Resquest Method 为 PUT。

本节代码示例:

github.com/qufei1993/http-protocol/tree/master/example/cors/02
CORS 与认证

对于跨域的 XMLHttpRequest 或 Fetch 请求,浏览器是不会发送身份凭证信息的。例如我们要在跨域请求中发送 Cookie 信息,就要做些设置:

为了能看到效果,我先自定义了一个 cookie 信息 id=NodejsRoadmap。

重点是设置认证字段,本文中 fetch 示例设置 credentials: "include" 如果是 XMLHttpRequest 则设置 withCredentials:"include"

经过以上设置,浏览器发送实际请求时会向服务器发送 Cookies,同时服务器也需要在响应中设置 Access-Control-Allow-Credentials 响应头

res.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:3010',
'Access-Control-Allow-Credentials': true

如果服务端不设置浏览器就不会正常响应,会报一个跨域错误,如下所示:

Access to fetch at 'http://127.0.0.1:3011/api/data' from origin 'http://127.0.0.1:3010' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.

还有一点需要注意,如果我们在请求中设置了 credentials: "include" 服务端就不能设置 Access-Control-Allow-Origin: "*" 只能设置为一个明确的地址。

本节代码示例:

github.com/qufei1993/http-protocol/tree/master/example/cors/03
解决跨域问题的几种方法

通过上面的分析了解跨域产生的原因之后,解决其实并不难,上面的讲解中其实也提供了解决方案,例如在 Node.js 中我们可以设置响应头部字段 Access-Control-Allow-Origin、Access-Control-Expose-Headers、Access-Control-Allow-Methods 等,但是在实际开发中这样设置难免繁琐,下面介绍几种常用的解决方法。

使用 CORS 模块

在 Node.js 中推荐你使用 cors 模块 github.com/expressjs/cors[3]。

在我们本节的示例中,一直使用的 Node.js 原生模块来编写我们的示例,在引入 cors 模块后,可以按照如下方式改写:

const http = require('http');
const PORT = 3011;
const corsMiddleware = require('cors')({
origin: 'http://127.0.0.1:3010',
methods: 'PUT,DELETE',
allowedHeaders: 'Test-CORS, Content-Type',
maxAge: 1728000,
credentials: true,
http.createServer((req, res) => {
const { url, method } = req;
console.log('request url:', url, ', request method:', method);
const nextFn = () => {
if (method === 'PUT' && url === '/api/data') {
return res.end('ok!');
return res.end();
corsMiddleware(req, res, nextFn);
}).listen(PORT);

cors 在预检请求之后或在预检请求里并选项中设置了 preflightContinue 属性之后才会执行 nextFn 这个函数,如果预检失败就不会执行 nextFn 函数。

如果你用的 Express.js 框架,使用起来也很简单,如下所示:

const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors());
JSONP

浏览器是允许像 link、img、script 标签在路径上加载一些内容进行请求,是允许跨域的,那么 jsonp 的实现原理就是在 script 标签里面加载了一个链接,去访问服务器的某个请求,返回内容。

相比上面 CORS 模块,JSONP 只支持 GET 请求,显然是没有 CORS 模块强大的。

Nginx 代理服务器配置跨域

使用 Nginx 代理服务器之后,请求不会直接到达我们的 Node.js 服务器端,请求会先经过 Nginx 在设置一些跨域等信息之后再由 Nginx 转发到我们的 Node.js 服务端,所以这个时候我们的 Nginx 服务器去监听的 3011 端口,我们把 Node.js 服务的端口修改为 30011,简单配置如下所示:

server {
listen 3011;
server_name localhost;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'http://127.0.0.1:3010';
add_header 'Access-Control-Allow-Methods' 'PUT,DELETE';
add_header 'Access-Control-Allow-Headers' 'Test-CORS, Content-Type';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Content-Length' 0;
return 204;
add_header 'Access-Control-Allow-Origin' 'http://127.0.0.1:3010';
add_header 'Access-Control-Allow-Credentials' 'true';
proxy_pass http://127.0.0.1:30011;
proxy_set_header Host $host;

本节代码示例:

github.com/qufei1993/http-protocol/tree/master/example/cors/04
总结

如果你是一个前端开发者,在工作难免会遇到跨域问题,虽然它属于浏览器的同源策略限制,但是要想解决这问题还需浏览器端与服务端的共同支持,希望读到这篇文章的读者能够理解跨域产生的原因,最后给予的几个解决方案,也希望能解决你对于跨域这个问题的困惑。

作者简介:五月君,软件设计师,公众号「Nodejs技术栈」作者。

参考资料

[1]Fetch Metadata Request Headers: https://w3c.github.io/webappsec-fetch-metadata/

[2]“MDN 文档 developer.mozilla.org/en-US/docs/Web/HTTP/CORS 简单请求”: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

[3]github.com/expressjs/cors: https://github.com/expressjs/cors

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
距离美国海岸仅169公里!俄核潜艇率队抵达古巴:没带核弹,带了3种导弹

距离美国海岸仅169公里!俄核潜艇率队抵达古巴:没带核弹,带了3种导弹

红星新闻
2024-06-13 20:09:21
世预赛18强分档出炉,中国毫无意外,避开死亡之组,可晋级世界杯

世预赛18强分档出炉,中国毫无意外,避开死亡之组,可晋级世界杯

刺头体育
2024-06-13 19:49:18
国足刚晋级18强赛,亚足联就送来大礼,有望直接让中国队进世界杯

国足刚晋级18强赛,亚足联就送来大礼,有望直接让中国队进世界杯

罗掌柜体育
2024-06-13 17:25:23
欧盟加征关税靴子落地,相关贸易损失或达40亿美元,中国电动汽车影响几何

欧盟加征关税靴子落地,相关贸易损失或达40亿美元,中国电动汽车影响几何

红星新闻
2024-06-13 15:13:50
暴赚530亿!144小时中国游,震碎老外三观

暴赚530亿!144小时中国游,震碎老外三观

金错刀
2024-06-13 14:59:49
游客瓦屋山被砸身亡后续:女孩身份曝光,父母已知情,崩溃难接受

游客瓦屋山被砸身亡后续:女孩身份曝光,父母已知情,崩溃难接受

180°视角
2024-06-13 20:43:07
请不要以保护我们安全的名义刁难我们!

请不要以保护我们安全的名义刁难我们!

顾礼先生
2024-06-13 15:49:50
27岁女子在家被捅死,保安被指“不作为”,众大V:保安没有执法权

27岁女子在家被捅死,保安被指“不作为”,众大V:保安没有执法权

可达鸭面面观
2024-06-13 09:18:50
2011年妻子和情夫通奸时,丈夫也要来,妻子说住不下了被丈夫杀死

2011年妻子和情夫通奸时,丈夫也要来,妻子说住不下了被丈夫杀死

汉史趣闻
2024-06-12 07:29:30
教育部不批准福耀科技大学,却批准西湖大学,属实非常正确!

教育部不批准福耀科技大学,却批准西湖大学,属实非常正确!

咖啡店的老板娘
2024-06-13 21:17:16
女排爆大冷!日本遭翻盘:2-0到2-3,中国女排重返亚洲第1+进奥运

女排爆大冷!日本遭翻盘:2-0到2-3,中国女排重返亚洲第1+进奥运

侃球熊弟
2024-06-13 20:41:33
后续!女孩瓦屋山飞石砸亡:身份被曝光,家境太凄惨,目击者发声

后续!女孩瓦屋山飞石砸亡:身份被曝光,家境太凄惨,目击者发声

影像温度
2024-06-13 17:26:28
新加坡门将餐厅收款码被中国球迷隔空刷爆,已被银行暂停使用

新加坡门将餐厅收款码被中国球迷隔空刷爆,已被银行暂停使用

互联网大聪明
2024-06-13 23:40:18
心疼!山区的孩子把AI当做“爱”,2024高考作文跑题的方式太苦涩

心疼!山区的孩子把AI当做“爱”,2024高考作文跑题的方式太苦涩

妍妍教育日记
2024-06-12 13:59:16
欧冠亚军主帅离任!多特官方:接受主帅泰尔齐奇的辞职请求

欧冠亚军主帅离任!多特官方:接受主帅泰尔齐奇的辞职请求

直播吧
2024-06-13 19:12:05
爆冷!一中专生闯进阿里数赛全球12强,身后全是清北麻省剑桥学生

爆冷!一中专生闯进阿里数赛全球12强,身后全是清北麻省剑桥学生

育学笔谈
2024-06-13 14:37:24
官宣!多特宣布欧冠亚军教头下课,3年2次被解雇,35岁功臣继任

官宣!多特宣布欧冠亚军教头下课,3年2次被解雇,35岁功臣继任

阿超他的体育圈
2024-06-13 19:19:26
出租车司机撞烂合伙人的保时捷装病去医院,民警查出“隐情”

出租车司机撞烂合伙人的保时捷装病去医院,民警查出“隐情”

澎湃新闻
2024-06-13 14:32:29
绝望!60岁华人夫妇从多伦多回中国养病, 再返回加拿大被拒入境

绝望!60岁华人夫妇从多伦多回中国养病, 再返回加拿大被拒入境

鬼谷子思维
2024-06-13 14:42:44
网传深圳一19岁小伙突然坠楼!疑似高考引发的悲剧,现场惨烈…

网传深圳一19岁小伙突然坠楼!疑似高考引发的悲剧,现场惨烈…

火山诗话
2024-06-13 20:12:27
2024-06-14 00:28:49
Nodejs开发
Nodejs开发
分享只有程序员懂的干货
648文章数 824关注度
往期回顾 全部

科技要闻

小红书员工仅1/5工龄满2年 32岁就不让进了

头条要闻

韩国女子20年前遭"集体性侵" 44名嫌犯无一人受到刑罚

头条要闻

韩国女子20年前遭"集体性侵" 44名嫌犯无一人受到刑罚

体育要闻

乔丹最想单挑的男人走了

娱乐要闻

森林北报案,称和汪峰的感情遭受压力

财经要闻

私募大佬孙强:中国为什么缺少耐心资本

汽车要闻

升级8155芯片 新款卡罗拉锐放售12.98-18.48万

态度原创

教育
游戏
数码
房产
手机

教育要闻

本科生收入揭秘:月薪六千成常态,高薪专业竟是这些!

主播请路过老人玩《无畏契约》:击杀就送鸡蛋等

数码要闻

高通 Adreno X1 规格揭晓,多款游戏中性能不输英特尔 Arc 核显

房产要闻

再度告急!海口连续仨月住宅入市不足千套!竟有楼盘卖爆!

手机要闻

四款中端新机再次被确认:一加、红米、真我和iQOO谁会成为佼佼者

无障碍浏览 进入关怀版