同源限制
浏览器安全的基石是“同源政策”(same-origin policy)。
含义
1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。
最初,它的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。
所谓“同源”指的是“三个相同”:
- 协议相同
- 域名相同
- 端口相同
举例来说,http://www.test.com/dir/page.html这个网址,协议是http,域名是www.test.com,端口是80(默认端口可以省略),它的同源情况如下:
- http://www.test.com/dir2/other.html:同源
- http://test.com/dir/other.html:不同源(域名不同)
- http://v2.www.test.com/dir/other.html:不同源(域名不同)
- http://www.test.com:88/dir/other.html:不同源(端口不同)
- https://www.test.com/dir/page.html:不同源(协议不同)
目的
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A 网站是一家银行,用户登录以后,A 网站在用户的机器上设置了一个 Cookie,包含了一些隐私信息(比如存款总额)。用户离开 A 网站以后,又去访问 B 网站,如果没有同源限制,B 网站可以读取 A 网站的 Cookie,那么隐私信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,同源政策是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。
限制范围
随着互联网的发展,同源政策越来越严格。目前,如果非同源,共有三种行为受到限制。
- 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB。
- 无法接触非同源网页的 DOM。
-
无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)。
不过有三个标签可以跨源请求资源:
在跨源访问时,浏览器开发者模式下查看网络,发现请求的状态为 200 ,但是出现了以下错误提示。
同源政策规定,AJAX 请求只能发给同源的网址,否则就报错。
除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。
- JSONP
- WebSocket
- CORS
JSONP
JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务端改造非常小。
- 全称是 JSON Padding.,请求时通过动态创建一个 Script,在 Script 中发出请求,通过这种变通的方式让请求资源可以跨域。
- 它不是一个官方协议,是一个约定,约定请求的参数里面如果包含指定的参数(默认是 callback),就说明是一个 JSONP 请求,服务器发现是 JSONP 请求,就会把原来的返回对象变成 JS 代码。JS 代码是函数调用的形式,它的函数名是 callback 的值,它的函数的参数就是原来需要返回的结果。
它的基本思想是,网页通过添加一个<script>元素,向服务器请求 JSON 数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。此种方式没有使用ajax技术。
我本地用nodejs写的一个简易的后端程序(支持jsonp请求),我们先在地址栏直接访问一下:
注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于 JSONP 是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
那么在页面中如何请求这个后端地址呢?
如下步骤:
-
网页插入<script>元素,由它向跨源网址发出请求。
-
网页插入<script>元素,由它向跨源网址发出请求。
-
2.由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了fn函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此也避免了使用JSON.parse的步骤。
控制台输出:
这段代码需注意的是:fn函数的定义必须放在跨源请求之前,否则会报如下错误:
原因:
JavaScript解释器在执行脚本时,是按块来执行的。通俗地说,就是浏览器在解析HTML文档流时,如果遇到一个<script>标签,则JavaScript解释器会等到这个代码块都加载完后,先对代码块进行预编译,然后再执行。执行完毕后,浏览器会继续解析下面的HTML文档流,同时JavaScript解释器也准备好处理下一个代码块。
虽然说,JavaScript是按块执行的,但是不同块都属于同一个全局作用域,也就是说,块之间的变量和函数是可以共享的。
跨源请求的script和fn函数定义的script是两个不同script块,那么跨源请求的script如果先执行,自然是找不到fn的。
也可以使用jquery的ajax方法,通过设置参数dataType="jsonp"来发也jsonp请求,如下:
如果后端不支持jsonp,则会报parseError的错误。
JSONP方式虽然简单易用,但有很大的局限,如JSONP 只能发GET请求, 并且后端必须支持jsonp的请求方式。
CORS
看跨域所报的错误,大概意思是说请求的资源上没有“Access-Control-Allow-Origin”头信息(此处说的是响应头)。
那么可以从这个地方考虑,在返回资源的时候加上这个头信息。这也就是接下来说的 CORS 请求的解决思路。
CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发GET请求,CORS 允许任何类型的请求。
CORS 是一个 W3C 标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制。
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能。
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。
以nodejs程序为例:
这里的"*"指所有域都允许,也可以指定具体的域。
WebSocket
WebSocket 是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
如下图中代码是使用nodejs编写,支持WebSocket:
同时要求客户端以ws的方式请求: