一 为什么需要跨域
同源策略是由 Netscape(网景) 提出的一个著名的安全策略,现在所有支持 JavaScript 的浏览器都会使用这个策略。
所谓同源是指域名、协议、端口都相同。以 http://www.xxxxx.com:80/ 为例,http:// 为协议,域名是 www.xxxxx.com,端口是80(提示:80 为默认端口,可以省略,若为其它端口则必须显示定义)。
为了安全,浏览器不允许进行跨域请求。当我们通过 Ajax 在网页和服务器之间发送或接收数据时,需要保证网页与所请求的地址是同源的,否则无法请求成功。同源策略可以防止 JavaScript 脚本从您的网站中读取数据,并将数据发送到其它的网站。如果没有同源策略,很有可能会有恶意的程序泄露您网站中的内容。
虽然同源策略在一定程度上提高了网站的安全,但也会给程序员带来一些麻烦,例如在访问一些开发接口时,由于同源策略的存在,会调用失败。要解决这种问题就需要用到跨域,跨域的方法有许多种,其中最经典的就是 JSONP和CORS。
二 JSONP详解
JSONP 全称“JSON with Padding”,译为“带回调的JSON”,它是 JSON 的一种使用模式。通过 JSONP 可以绕过浏览器的同源策略,进行跨域请求。
在进行 Ajax 请求时,由于同源策略的影响,不能进行跨域请求,而 <script> 标签的 src 属性却可以加载跨域的 JavaScript 脚本,JSONP 就是利用这一特性实现的。与普通的 Ajax 请求不同,在使用 JSONP 进行跨域请求时,服务器不再返回 JSON 格式的数据,而是返回一段调用某个函数的 JavaScript 代码,在 src 属性中调用,来实现跨域。使用JSONP进行通信时的大体流程如下:
2.1 什么是 JSONP 回调格式?
浏览器在遇到 script 元素时向源 URL 发送 HTTP 请求后,来自服务器的响应是封装在函数调用中的 JSON,如下所示:
- 在 HTML 代码中插入脚本标记。从中检索数据的 URL 将是此脚本标记的源。可以为 Web 服务指定回调函数。
- 在 URL 末尾插入回调参数。浏览器解析 JSON 答案(这是一个字符串)并将其转换为 JavaScript 对象。生成的对象被提供给回调函数,然后调用该回调函数。
要从服务器接收数据,请使用 XMLHttpRequest (XHR) 方法。
检索数据后,JSON.parse() 方法可以将 JSON 表达式转换为 JavaScript 对象。
然而,XHR 也有同源的安全问题。这意味着,如果您从域(例如 ADomain.com)请求页面,则该页面将执行 XMLHttpRequest 以从 BDomain.com 获取数据,浏览器将拒绝它。这是因为该请求被发送到一个域,而不是提供该页面的域。仅当 XMLHttpRequest 发送到 ADomain.com 时才会返回数据,因为 XHR 仅在向同一域发出请求时才运行。
2.2 什么是 JSONP 回调?
JSONP 是一种非正式协议,允许您通过在当前页面上生成脚本标记并等待对您提供的回调处理程序的响应来进行跨域调用。
这个概念是,此代码动态地将<script>标签插入到页面中,并且当此代码加载时,它会导致执行服务器加载的 JavaScript。DOM 和<script>元素用于绕过 XHR 对象的跨站点脚本约束。
服务器预计会传送 JSON 数据,但有一个警告:回调函数名称(作为 URL 的一部分提供)必须放在前面。
您可以将回调查询参数与任何符合条件的 API 路由结合使用,以接收封装在 JSONP 回调函数中的 API 响应。数据将与您指定的回调函数一起返回。
任何有效的 JavaScript 方法名称都可以用作回调值。完整的 JSON API 响应将包装在请求的回调函数中。
2.3 为什么我们需要 JSONP 回调?
现在大量 API 提供商都支持 JSONP 查询。这是因为大多数 Internet 浏览器在使用基本 Ajax 时都会阻止跨域查询。
例如,如果您网站的域名是“a.com”,它将使用 a.com 上托管的 JavaScript。大多数网络浏览器会立即将 a.com JavaScript 为完成 b.com 上的请求而进行的“AJAX 调用”标记为不安全并禁用它。这是互联网浏览器实施的“同源策略”,旨在防止危险脚本将数据传输到单独的域。因为您需要 a.com JavaScript 来访问 b.com 来执行您的服务,所以这似乎是一个重大问题,JSONP 可以为您提供帮助!
JSONP 的“P”(通用基本 JSON 的“填充”或“前缀”)是对 parseResponse() 的函数调用。服务器必须使用包含 JSONP 函数的响应进行响应,以便 JSONP 运行。
对于 JSON 格式的结果,JSONP 不起作用。相反,客户端和服务器就传回的 JSONP 函数激活以及函数获取的有效负载达成一致。查询网站通常可以选择通过发送 JSON 数据的服务器来命名 JSONP 函数,并使用术语 JSONP 或回调作为指定的查询字符串参数。
JSONP 是为了响应同源策略 (SOP) 而创建的,该策略指定如果从一个域提供 HTML 页面,则该网页一旦发送就无法对单独域上的网站进行“AJAX 调用”给客户。
JSONP 调用需要回调才能工作。该文件加载在脚本标记中,如果代码不是方法调用的格式,则结果将是一个将被删除的对象,并且永远不会调用成功回调函数。
2.4 如何使用 JSONP 回调?
JSONP 可以通过以下方式使用:
- 在 HTML 代码中包含脚本标记。从中检索数据的 URL 将是此脚本标记的源。可以为 Web 服务指定回调函数。在 URL 末尾包含回调参数。
- 为了遇到 script 元素,浏览器将 HTTP 请求传输到源 URL。
- 来自服务器的响应是包含在函数调用中的 JSON。
- 浏览器解析字符串形式的 JSON 答案,并将其转换为 JavaScript 对象。生成的对象被提供给回调函数,然后调用该回调函数。
2.5 JSONP 回调的企业使用情况如何?
JSONP 的优点是不受同源策略的约束,就像 XMLHttpRequest 对象放置的 AJAX 请求一样;它具有良好的集成性,可以在旧版浏览器中运行,并且不需要 XMLHttpRequest 或 ActiveX 支持。它也不涉及 XMLHttpRequest 或 ActiveX 支持。请求的结果可以在完成后通过调用回调来发送。
JSONP的缺点是只支持GET请求,不支持POST等其他HTTP请求;它只允许跨域HTTP请求。此外,它无法解决如何从同一页面上的两个单独域调用 JavaScript 的问题。
三 CORS详解
虽然JSONP解决了跨域数据请求的问题,但它并不是一个标准化的解决方案,并且有一些缺点,比如安全性问题(容易受到注入攻击),以及只能用于GET请求的限制。随着时间的推移,CORS(Cross-Origin Resource Sharing,跨源资源共享)成为了一种更为安全和灵活的解决方案,允许服务器明确地指定哪些域可以访问资源,从而不再需要使用JSONP。
如今,JSONP的使用已经大大减少,大部分现代Web应用都采用CORS,因为它提供了更好的安全性和控制跨域请求的能力。不过,了解JSONP的历史和原理对于理解Web开发中的跨域请求处理是有帮助的。
CORS是基于HTTP1.1的一种跨域解决方案,它的全称是Cross-Origin Resource Sharing,跨域资源共享。
它的总体思路是:如果浏览器要跨域访问服务器的资源,需要获得服务器的允许
而要知道,一个请求可以附带很多信息,从而会对服务器造成不同程度的影响。比如有的请求只是获取一些新闻,有的请求会改动服务器的数据。针对不同的请求,CORS规定了三种不同的交互模式,分别是:
- 简单请求
- 需要预检的请求
- 附带身份凭证的请求
这三种模式从上到下层层递进,请求可以做的事越来越多,要求也越来越严格,下面分别说明三种请求模式的具体规范。
3.1 简单请求
当浏览器端运行了一段ajax代码(无论是使用XMLHttpRequest还是fetch api),浏览器会首先判断它属于哪一种请求模式
当请求同时满足以下条件时,浏览器会认为它是一个简单请求:
- 请求方法属于下面的一种:get、post、head
- 请求头仅包含安全的字段,常见的安全字段如下:Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width
- 请求头如果包含Content-Type,仅限下面的值之一:text/plain、multipart/form-data、application/x-www-form-urlencoded
3.2 需要预检的请求
简单的请求对服务器的威胁不大,所以允许使用上述的简单交互即可完成。
但是,如果浏览器不认为这是一种简单请求,就会按照下面的流程进行:
- 浏览器发送预检请求,询问服务器是否允许
- 服务器允许
- 浏览器发送真实请求
- 服务器完成真实的响应
比如,在页面http://my.com/index.html中有以下代码造成了跨域
浏览器发现它不是一个简单请求,则会按照下面的流程与服务器交互,浏览器发现它不是一个简单请求,则会按照下面的流程与服务器交互
- 浏览器发送预检请求,询问服务器是否允许
可以看出,这并非我们想要发出的真实请求,请求中不包含我们的响应头,也没有消息体。
这是一个预检请求,它的目的是询问服务器,是否允许后续的真实请求。
预检请求没有请求体,它包含了后续真实请求要做的事情
预检请求有以下特征:
- 请求方法为OPTIONS
- 没有请求体
- 请求头中包含
- Origin:请求的源,和简单请求的含义一致
- Access-Control-Request-Method:后续的真实请求将使用的请求方法
- Access-Control-Request-Headers:后续的真实请求会改动的请求头
- 服务器允许
服务器收到预检请求后,可以检查预检请求中包含的信息,如果允许这样的请求,需要响应下面的消息格式
对于预检请求,不需要响应任何的消息体,只需要在响应头中添加:
- Access-Control-Allow-Origin:和简单请求一样,表示允许的源
- Access-Control-Allow-Methods:表示允许的后续真实的请求方法
- Access-Control-Allow-Headers:表示允许改动的请求头
- Access-Control-Max-Age:告诉浏览器,多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求了
- 浏览器发送真实请求
预检被服务器允许后,浏览器就会发送真实请求了,上面的代码会发生下面的请求数据
- 服务器响应真实请求
可以看出,当完成预检之后,后续的处理与简单请求相同
下图简述了整个交互过程
3.3 附带身份凭证的请求
默认情况下,ajax的跨域请求并不会附带cookie,这样一来,某些需要权限的操作就无法进行
不过可以通过简单的配置就可以实现附带cookie
这样一来,该跨域的ajax请求就是一个附带身份凭证的请求
当一个请求需要附带cookie时,无论它是简单请求,还是预检请求,都会在请求头中添加cookie字段
而服务器响应时,需要明确告知客户端:服务器允许这样的凭据
告知的方式也非常的简单,只需要在响应头中添加:
Access-Control-Allow-Credentials: true即可
对于一个附带身份凭证的请求,若服务器没有明确告知,浏览器仍然视为跨域被拒绝。
另外要特别注意的是:对于附带身份凭证的请求,服务器不得设置
Access-Control-Allow-Origin 的值为*。这就是为什么不推荐使用*的原因
四 关于同源和标签的一些问题
4.1 Ajax、XHR、同源策略的关系
优质回答:Ajax(Asynchronous JavaScript + XML)即异步JavaScript加XML技术;XHR即XMLHttpRequest对象,该对象的作用就是实现发送服务器请求和获取响应异步从服务器获取数据,该技术是推动Ajax发展的关键技术;同源策略是浏览器站在安全的角度制定的一种从服务器获取数据的限制,这个安全限制可以防止一些恶意行为,通过XHR进行Ajax通信会被同源策略限制。
4.2 为什么<script>![]()
优质回答:
1.<script>
2.同源策略不是统一的干净公理,它实际上是一组特定的规制和特殊情况,这些规则和特殊情况已经成为现代web的化石,您无法使用正确捕获基本的细节的简短描述来定义同源策略,因为特殊情况在实践中实际上很重要
概括下来就是:<script>
4.3 为什么制定同源策略后不取消<script>![]()
优质回答:取消这种跨域能力会破坏很多现有的网络
4.4 通过<script>和
标签实现跨域请求的区别
优质回答:标签只能通过监听onload和onerror事件知道什么时候接收到了响应进而进行后续处理,但是无法获取数据;<script>是获取一个可执行的js程序,且在请求响应成功后会立刻执行,这就为有数据返回的跨域请求提供了可能。
参考资料
[1] JSONP详解:
https://blog.csdn.net/M_emory_/article/details/131504133
[2] 一文搞懂JSONP:
https://zhuanlan.zhihu.com/p/517367840
[3] 什么是JSONP回调:
https://weatherstack.com/jsonp-callbacks
[4] JSONP理解-图解:
https://juejin.cn/post/6844904149901918216
[5] CORS原理详解:
https://blog.csdn.net/qq_44197554/article/details/106454544