跨域总结
Opened this issue · 0 comments
敲黑板
同源政策:
相同协议(http/https)、域名(www.baidu.com)、端口
限制范围:
Cookie、LocalStorage、IndexDB无法读取
DOM无法获得
Ajax请求不能发送
这样的限制会导致使用不便,可以合理使用方法规避。
对于Cookie:
如果两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享cookie
A网页:http://w1.example.com/a.html B网页:http://w2.example.com/b.html
两个网页设置相同的domain document.domain = example.com 即可。
此方法适用于cookie与iframe,LocalStorage IndexDB不可
另:服务器可以在设置cookie的时候,指定cookie所属域名为一级域名,比如.example.com 这样二级三级域名不用做任何设置。
Set-Cookie: key-value; domain=.example.com; path=/
对于iframe:
如果两个网页不同源,那么无法拿到对方的DOM。如果仅二级域名不同,则见上文即可解决。
若完全不同源网站,有如下3中方法可解决窗口的通信问题。
- 1、片段识别符
片段标识符指的是url中#后面的部分,比如 http://aa.example.com/a.html#fragment中的#fragment。如果只是改变片段标识符,页面不会刷新。
父窗口可以把信息写入子窗口的片段标识符。
var src = originalURL + ‘#’ + data
document.getElementById(‘myIframe’).src = src
那么子窗口通过监听hashchange事件得到通知。
window.onhashchange = checkMessage
function checkMessage(){ var message = window.location.hash //…..}
同样,子窗口也可以改变父窗口的片段标识符
parent.location.href = target + “#”+ hash
- 2、window.postMessage
html5引入的解决问题api,为window对象新增方法,允许跨窗口通信,不论两个窗口是否同源。
语法:otherWindow.postMessage(message, targetOriginal, [transfer])
其中message:信息
targetOriginal:设置你需要将信息传递到那些窗口 如果* 则不限制域名向所有窗口发送 。通过“协议+域名+端口”来匹配。
transfer:是一串和message同时传递的对象,这些对象的所有权被转移给消息的接收方,而发送方不再保有所有权。
父窗口http://aa.com向子窗口http://bb.com
var popup = window.open(‘http://bb.com’, ’title')
popup.postMessage(‘Hello World!’, ‘http://bb.com')
子窗口向父窗口发送消息
window.opener.postMessage(’Nice to see you’, ‘http://aa.com')
父窗口和子窗口都可以通过message事件,监听对方的消息。
message事件的事件对象MessageEvent有四个属性:
message 表示message的类型
data 表示window.postMessage的第一个参数
origin 表示调用window.postMessage()方法时调用页面的当前状态
source 记录调用window.postMessage()方法的窗口信息
另:window.name
浏览器有一个属性window.name 无论是否同源,只要在同一个窗口打开,前一个网页设置,后一个即可读取它。并且容量很大,可以放很长的字符串(2M)。
例如我打开一个窗口,输入 www.baidu.com 然后存入 window.name = “JSZ” 接着我换链接为 www.douban.com 此时打开控制台 window.name 还保存着我的信息 JSZ
父窗口(www.b.html)打开一个隐藏的子窗口(www.a.html),子不跨域获取数据,执行
window.name = data
接着,子窗口跳回一个与主窗口同域的网址,这样,同源的窗口可互相访问window.name
location = ‘http://parent.url.com/xxx.html'
然后主窗口就可以读取子窗口的window.name
var data = document.getElementById(‘myFrame’).contentWindow.name
缺点是必须监听子窗口window.name属性的变化,影响性能。
对于localStorage:
使用h5的postMessage
对于Ajax:
方法:JSONP、WebSocket、CORS
- 1、JSONP
网页通过添加一个<script>元素,向服务器请求JSON数据,服务器收到请求后,将数据放入一个指定名字的回调函数传回来。只能get请求。
src = ‘http://aa.example.com?callback=foo'
function foo(data){//...}
- 2、WebSocket
WebSocket是一种通信协议,该协议不实行同源政策,只要服务器支持,就可以通过它进行跨域通信。
浏览器通过JS向服务端发出建立WebSocket的请求,连接成功后,服务端客户端可通过TCP连接传输数据。允许服务器主动向客户端发送信息
js主要代码:
申请一个WebSocket对象
var wsUri = ‘ws://echo.websocket.org/'
websocket = new WebSocket(wsUri)
WebSocket对象支持4个消息 onopen onmessage onclose onerror
websocket.onopen = function(evt){ //… } 客户端与服务器连接成功,触发onopen消息
连接失败,发送、接收数据、处理数据出现错误 onerror
Browser接收到WebSocketServer发送过来的数据 onmessage
Browser接收到WebSocketServer发送的关闭连接请求 onclose
- 3、CORS 最主要的解决AJAX的方法
对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息。因此,只要服务器实现了CORS接口就可以跨源通信。
浏览器将CORS请求分为两类:简单请求和非简单请求。
那么如何区分呢,满足如下条件即为简单请求:
(1)请求方法是以下三种之一:HEAD、GET、POST
(2)HTTP的头信息不超出以下几种字段:
Accept Accept-Language Content-Language Last-Event-ID
Content-Type(只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
不同时满足的即为非简单请求。
对于简单请求:
浏览器直接发出CORS请求,在头信息中增加一个Origin字段,用来说明本次请求来自哪个源(协议+域名+端口),服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源不在许可范围内,服务器会返回一个正常的HTTP回应,浏览器根据是否有Access-Control-Allow-Origin字段,判断是否出错。若没有,从而抛出错误,被XMLHttpRequest的onerror回调函数捕获。
如果Origin指定的源在范围内,服务器返回响应,多出几个头信息字段。
Access-Control-Allow-Origin: http://example.com *或请求的Origin字段值
Access-Control-Allow-Credentials: true 表示是否允许发送Cookie 简略下文1-1-2
Access-Control-Expose-Headers: FooBar 可选字段,简略下文1-1-1
Content-Type: text/html; charset=utf-8
1-1-1:CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,必须指定,如上,可以返回FooBar字段的值。
1-1-2:只能设置为true表示允许发送Cookie,不允许则删掉该字段即可。
若要允许Cookie,同时要在AJax请求中打开withCredentials属性,并且Access-Control-Allow-Origin不能为*
var xhr = new XMLHttpRequest() xhr.withCredentials = true
对于非简单请求:
如请求方法为PUT或DELETE,或者Content-Type字段类型为application/json
非简单请求会在正式通信之前,增加一次HTTP查询请求,称为”预检“请求。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
例子:
请求方法PUT 并发送一个自定义头信息 X-Custom-Header
浏览器发送”预检“请求。
”预检“请求用的方法是OPTIONS,表示这个请求是用来询问的。
Access-Control-Request-Method 用来列出请求会用到哪些HTTP方法
Access-Control-Request-Headers 逗号分隔的字符串,指定额外发送的头信息字段。
”预检“请求的回应:
服务器检查Origin、Access-Control-Request-Method、Access-Control-Request-Headers字段,确认允许跨域请求,就可以做出如上回应。Access-Control-Allow-Origin若为*表示同意任意跨源请求。
如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。
一旦服务器通过了”预检“请求,以后每次浏览器正常的CORS请求都和简单请求一样了。
CORS设置
可以有以下3种方法设置,用于不同的业务场景
IIS服务器配置
弹出后,点击添加按钮,设置Access-Control-Allow-Origin值为*
配置文件更改
Node.js
app.all('*', function(req, res, next) {
if( req.headers.origin == 'https://www.google.com' || req.headers.origin == 'https://www.baidu.com' ){
res.header("Access-Control-Allow-Origin", req.headers.origin);
res.header('Access-Control-Allow-Methods', 'POST, GET');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
res.header('Access-Control-Allow-Headers', 'Content-Type');
}
next();
});