1. 概念
两个页面同源需要满足以下三个条件:
- 协议(protocol)相同: 同为
https
或http
- 端口相同: 如未指定,
http
默认为80端口,https
默认为443端口 - 域名相同
以https://www.gogoing.site/index.html为例来说明以下示例是否存在同源限制:
1. https://www.gogoing.site/articles/1.html // 同源
2. http://www.gogoing.site/index.html // 不同源,协议不同
3. https://www.gogoing.site:464/index.html // 不同源,端口不同
4. https://api.gogoing.site/index.html // 不同源,域名不同
2. 同源策略功能
为了保护网站信息的安全,一个源下的JS脚本无法获取另外一个源下的资源。
Cookie
、localStorage
、sessionStorage
访问限制DOM
获取限制AJAX
请求限制(跨域问题)
3. 不受同源限制的标签
link
、script
、img
、iframe
不受同源限制影响,可以加载其他源下的资源。
4. 跨域及解决方案
由于同源策略的限制,一个域下的JS脚本无法向另外一个域发送AJAX请求。
跨域有以前常用解决方案:
- 通过JSONP跨域
- 使用postMessage跨域传递消息
- 跨域资源共享(CORS)
- Nginx反向代理
- WebSocket通信(允许跨域通信,但需要服务器允许)
4.1. JSONP
由于script标签不受同源限制影响,从而我们可以通过动态创建script来请求一个带参数的网址实现跨域通信。实现方法如下:
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://www.gogoing.site/api/auth?username=user&callback=authCallback`
document.body.appendChild(script)
function authCallback(data) {
console.log(data)
}
通过该链接请求服务器返回回来的内容的script
脚本,如以下示例内容:
authCallback({code: 200, data: {...}})
浏览器在加载该脚本后将会执行authCallback
方法,从而实现跨域。
缺点:该方法只支持GET
请求且需要服务器配合
4.2. postMessage
4.2.1 说明
postMessage
是HTML5
XMLHttpRequest Level 2中的API,且是为数步度可以跨域操作的window属性之一。它可以用于解决以下问题:
- 页面和其打开的子窗口之间的数据传递
- 页面与嵌套的
iframe
之间的消息传递 - 多窗口(frames)之间消息传递
- 以上三个场景的跨域数据传递
4.2.2 用法
postMessage(data, targetOrigin)方法接收两个参数:
- data: 支持任意类型或可复制的对象。但由不部分浏览器只支持字符串,所以最好使用JSON.stringify序列化
- targetOrigin: 通过窗口的origin属性来指定哪些窗口可以获取到消息,也可以是字符串
*
(表示任意窗口)或/
(指定和当前窗口同源)或一个URI
- 页面和子窗口之间的通信
// 父窗口中
const win = window.open('https://www.gogoing.site', 'postMessage', 'width=500px&height=500px')
win.postMessage('https://www.gogoing.site', 'message from parent window')
// 子窗口
window.addEventListener('message', e => console.log(e), false)
- 页面和
iframe
之间通信
// html
<iframe src="https://www.gogoing.site/index.html" id="iframe"></iframe>
// js
const iframe = document.getElementById('iframe')
// 向iframe发送消息
iframe.contentWindow.postMessage('message', 'https://www.gogoing.site')
// iframe向父页面发送消息
window.parent.postMessage('message from iframe', 'https://www.gogoing.site')
- 多窗口(frames)之间消息传递,
HTML5
中已经不支持frameset
<frameset cols="25%,50%,25%">
<frame src="frame_a.htm" />
<frame src="frame_b.htm" />
<frame src="frame_c.htm" />
</frameset>
// 在父页面中通过`window.frames[index]`来访问子frame的window
window.frames[0].postMessage('message', '/')
// 在子页面中,通过`window.parent`访问父页面的window
window.parent.postMessage('message', '/')
4.3. 跨域资源共享(CORS)
CORS允许浏览器向跨源服务器发出XMLHttpRequest
请求,它的基本思想就是使用自定义的HTTTP
请求头让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。CORS需要浏览器和服务器的同时支持,目前所有浏览器(IE浏览器不低于IE10)都支持该功能。因为,只需在服务器进行相应设置,就可以允许跨域通信。
4.3.1 前端请求配置
在跨域请求时,浏览器会先发送一次预请求(OPTIONS
请求,不携带任何数据),在响应中,如果服务器端允许跨域,则发送真实的请求(GET, POST, PUT等)。如果服务器不允许跨域,则会抛出错误且不会发送真实请求。
var xhr = new XMLHttpRequest()
// withCredentials: 是否携带cookie,如果设置为true,服务器端不能将Access-Control-Allow-Origin设置为*
xhr.withCredentials = true
4.3.2 Node.js中配置
// 以express为例,配置如下
res.header('Access-Control-Allow-Origin', 'www.gogoing.site') // 设置允许跨域的域名,* 代表所有域名
res.header('Access-Control-Allow-Credentials', true) // 允许携带cookie,如果为true, Access-Control-Allow-Origin不能设置为*
res.header('Access-Control-Allow-Headers', 'X-Requested-With, CONTENT-TYPE, Authorization') // 允许的请求头
res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS') // 允许的请求方法