什么是域?
出于安全的考虑,浏览器同源策略会限制来自不同域名、协议、端口(不同的域)资源之间的访问,这个时候,我们就需要跨域访问其他不同域上服务器的资源。
下表展示了相对 http://bbs.mafengshe.com/a/b.html 同源检测的示例:
URL | 结果 | 原因 |
---|---|---|
http://bbs.mafengshe.com/a/b/c.html | 成功 | 相同域名 |
http://bbs.mafengshe.com/b/c.html | 成功 | 相同域名 |
https://bbs.mafengshe.com/a/b.html | 失败 | 不同协议 |
http://bbs.mafengshe.com:90/a/b.html | 失败 | 不同端口 |
http://work.mafengshe.com/a/b/c.html | 失败 | 不同域名 |
另外,如果是协议(http和https)或者端口的不同,前端js是无法实现跨域访问的,需要后台实现。
如何跨域?
想要跨域,首先要从同源策略下手,突破同源策略的限制。
使用document.domain更改源
我们可以通过document.domain设置当前域的值为自身或者是当前域的父级,设置成父域后,较短的域将用于后续的检查。
假设在 http://bbs.mafengshe.com/a/b.html 域下执行下面语句
document.domain = 'mafengshe.com'
这样,页面就能通过对 http://mafengshe.com/a/other.html 的同源检测。不过,document.domain 无法把 mafengshe.com 设成 otherdomail.com
浏览器单独保存端口号。任何的赋值操作,包括document.domain = document.domain
都会以null值覆盖掉原来的端口号。因此company.com:8080
页面的脚本不能仅通过设置document.domain = "company.com"
就能与company.com
通信。赋值时必须带上端口号,以确保端口号不会为null。
注意:使用document.domain允许子域安全访问其父域时,您需要设置document.domain在父域和子域中具有相同的值。这是必要的,即使这样做只是将父域设置回其原始值。否则可能会导致权限错误。
使用document.name 存储信息
window
对象有个name
属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面(甚至不同域名)都是共享一个window.name
的,每个页面对window.name
都有读写的权限,window.name
是持久存在一个窗口载入过的所有页面中的,所以我们可以通过window.name在不同iframe间传递信息,name可以存储2MB的信息。
通过JSONP实现跨域
JSONP(JSON with Padding)是JSON的一种“使用模式”,而 HTML 的<script>
元素是一个例外。利用 <script>
元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。
JSONP(JSON with Padding)是JSON的一种“使用模式”,JSONP由回调函数和数据组成,该数据就是函数的参数。
因为同源策略的缘故,我们无法访问访问非同源的资源,但是HTML 的<script>
元素是一个例外。利用 <script>
元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。
/**原理如下:
* 通过script标签请求js
* 后台获取get的参数
* 后台返回带上参数的fetchData(data)
* 浏览器在script请求完成后,返回的函数会被执行,参数就传过来了
* 至此跨域通信完成
**/
function fetchData(res){
console.log('The responsed data is: '+ res.data);
}
var script = document.createElement('script');
script.src = 'http://www.baidu.com/json/?callback=fetchData';
document.body.insertBefore(script, document.body.firstChild);
JSONP的优缺点
JSONP的优点是:它不像XMLHttpRequest
对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript
调用的问题。
优点:
- 不像
XMLHttpRequest
对象实现的Ajax请求那样受到同源策略的限制 - 兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持
- 数据调用获取简单
缺点:
只支持GET请求而不支持POST等其它类型的HTTP请求
它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行
JavaScript
调用的问题安全性不能保证
调用失败的时候无法返回错误码
跨域资源共享(CORS)
什么是CORS?
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。
CORS实现跨域的原理
CORS背后的思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是成功还是失败
CORS跨域的实现需要前浏览器端和服务器端共同支持。服务器通过设置Access-Control-Allow-Origin
的值为可被允许的源或者*
,当浏览器获取到该头字段信息,就会允许AJax跨域访问了。
对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
非简单请求
首先先介绍简单请求这一概念;满足以下条件即为简单请求
- HTTP方法是下列之一
HEAD GET POST
- 不能人为设置除下面以外的请求头:
Accept Accept-Language Content-Language Content-Type (需要注意额外的限制) DPR Downlink Save-Data Viewport-Width Width
- 请求头中的Content-Type请求头的值是下列之一
application/x-www-form-urlencoded multipart/form-data text/plain
不满足以上要求的请求,即为非简单请求。非简单请求是可能修改服务器资源的请求,比如请求方法是PATCH,PUT或DELETE; 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
服务器端需要返回注入下面的 response header:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH
Access-Control-Allow-Headers: X-Custom-Header
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,进行跨域资源访问。
与JSONP的比较
- JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求
- 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理
- JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)
使用HTML5的window.postMessage方法跨域
window.postMessage(message,targetOrigin)
方法是html5
新引进的特性,可以使用它来向其它的window
对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera
等浏览器都已经支持window.postMessage
方法。