基本概念
什么是跨域?
跨源资源共享 (CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。
CORS
全称 Cross-Origin Resource Sharing,意为跨域资源共享。当一个资源去访问另一个不同域名或者同域名不同端口的资源时,就会发出跨域请求。
简单来说,就是当你访问一个 URL 的协议、域名、端口
三者之间,任意一个与当前页面 URL 的不同时,即为跨域。
什么是同源策略?
同源策略是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。
它能帮助阻隔恶意文档,减少可能被攻击的媒介。例如,它可以防止互联网上的恶意网站在浏览器中运行 JS 脚本,从第三方网络邮件服务(用户已登录)或公司内网(因没有公共 IP 地址而受到保护,不会被攻击者直接访问)读取数据,并将这些数据转发给攻击者。
如果两个 URL 的协议、端口(如果有指定的话)和主机都相同的话,则这两个 URL 是同源的。
下表给出了与 URL http://store.company.com/dir/page.html
的源进行对比的示例:
URL | 是否同源 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html |
是 | 只有路径不同 |
http://store.company.com/dir/inner/another.html |
是 | 只有路径不同 |
https://store.company.com/secure.html |
否 | 协议不同 |
http://store.company.com:81/dir/etc.html |
否 | 端口不同(http:// 默认端口是 80) |
http://news.company.com/dir/other.html |
否 | 主机不同 |
上述内容摘录自MDN
,简单理解的话,URL 一般长这样: https://www.baidu.com:8080
,主要由 协议(https)
,域名/主机(www.baidu.com)
还有 端口号(8080)
组成,如果是默认端口号,那可以省略不写, http 的默认端口号是 80
,https 的默认端口号是 443
。
当两个 URL 的 协议、域名、端口号
全部相同时,说明这两个 URL 才是 同源
的。
同源策略的限制
- 数据:无法读取 Cookie、LocalStorage 和 IndexDB。
- DOM:无法获得 DOM。
- 网络请求:不能发送 AJAX 请求。
如何解决跨域问题?
JSONP
JSONP(JSON with Padding)是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。
它的基本思想是,网页通过添加一个<script>
元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
简单实现,直接添加<script>
元素,代码如下:
html
<html>
<head>
<title> document </title>
<script type="text/javascript" src="http://example.com/foo.js"></script>
</head>
<body> ... </body>
</html>
动态插入<script>
元素,代码如下:
js
// 1. 定义一个 回调函数 handleResponse 用来接收返回的数据
function handleResponse(data) {
console.log(data);
};
// 2. 动态创建一个 script 标签,并且告诉后端回调函数名叫 handleResponse
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.src = 'http://example.com/json?callback=handleResponse';
body.appendChild(script);
// 3. 通过 script.src 请求 http://example.com/json?callback=handleResponse,
// 4. 后端能够识别这样的 URL 格式并处理该请求,然后返回 handleResponse({"name": "hello jsonp"}) 给浏览器
// 5. 浏览器在接收到 handleResponse({"name": "hello jsonp"}) 之后立即执行 ,也就是执行 handleResponse 方法,获得后端返回的数据,这样就完成一次跨域请求了。
虽然
Img标签
由于 img
标签不受浏览器同源策略的影响,允许跨域引用资源。因此可以通过 img 标签的 src 属性进行跨域。
html
<html>
<head>
<title> document </title>
</head>
<body>
<img src="http://example.com/foo.jpg"/>
</body>
</html>
WebSocket
WebSocket是一种通信协议,使用ws://
(非加密)和wss://
(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自维基百科)。
makefile
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
上面代码中,有一个字段是Origin
,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin
这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。
makefile
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
CORS
CORS
是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET
请求,CORS允许任何类型的请求。
如何通过 CORS
完成跨源AJAX请求,可以参考 阮一峰 - 跨域资源共享 CORS 详解。
反向代理
浏览器有跨域限制,但是服务器不存在跨域问题。通过在请求到达服务器前部署一个服务,将接口请求进行转发,这就是反向代理。再由服务器将请求的资源返回给客户端。
可以参考: 一次说清-Nginx反向代理及参数配置、Nginx反向代理实践
其他的方法
document.domain
Document.domain 已弃用: 不再推荐使用该特性。虽然一些浏览器仍然支持它,但也许已从相关的 web 标准中移除,也许正准备移除或出于兼容性而保留。请尽量不要使用该特性,并更新现有的代码。请注意,该特性随时可能无法正常工作。
location.hash
Location
接口的 hash
属性返回一个 USVString
,其中会包含 URL 标识中的 '#'
和 后面 URL 片段标识符。
除此之外,也能被用于解决跨域问题,详情可以查看 location.hash解决跨域的原理
window.name
Window.name 用于获取/设置窗口的名称。
Window.name 的作用简述 在某些框架中(如:SessionVars 和 Dojo's dojox.io.windowName),该属性被用于作为JSONP的一个更安全的备选,来提供跨域通信。
早些年会实现,但现代 web 应用,推荐使用 postMessage API 进行敏感的跨域通信。
window.name 可以作为超链接和表单的 target,指定跳转到哪个页面。window.open() 指定新打开的窗口,如果open返回窗口对象,就可以设置新窗口的name,然后作为 target,可以指定跳到这个新窗口。也可以在 window.open() 传入第二个参数,这个参数将会作为新窗口的name。也可以当 target 。但是注意在服务器部署后使用 window.open 没有生成新窗体。
js
string = window.name;
window.name = string;
window.name
的值是跟着浏览器窗体走的,因此,只要在一个窗体中,就可以共享一个值,于是可以实现跨域数据获取,这个在以前还是挺有名的一个跨域方法,但现在使用 window.name
实现跨域通信已经属于鸡肋方法了,可以使用 postMessage跨域跨文档通信 代替,更好用更安全更强大。
当然你如果想了解 window.name
,也可以参考下文:
总结:
window.name
可读可写,指支持字符串;window.name
的值跟着浏览器窗口走的,不是跟着页面走的;window.name
没什么卵用,知道他没用就是很有用的知识。
postMessage
window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain
设置为相同的值) 时,这两个脚本才能相互通信。
window.postMessage(message,targetOrigin)
方法是 HTML5 新引进的特性,可以使用它来向其它的 window 对象发送消息,无论这个 window 对象是属于同源或不同源。
详情可以查看 postMessage解决跨域的原理