同源策略
一、概述
含义
“同源”指的是:
- 协议必须相同
- 域名必须相同
- 端口必须相同
目的
同源策略的目的是为了保证用户信息的安全,不被恶意的网站窃取数据
限制范围
- ajax请求
- dom无法获取
- cookie、localStorage、indexDB无法读取
二、Cookie
cookie是浏览器写入的一小段信息片段,只有同源的网页才能共享。大小限制是4k。两个页面的一级域名相同,二级域名不同时,可以通过设置相同的 document.domain,就可以共享cookie了。
document.domain = "example.com" cookie 和 iframe,localStorage 和 indexDB 并不适合用这种方式规避同源策略,需要使用postMessage api。服务器也可以在设置cookie的时候指定domain的字段为一级域名。
Set-Cookie: key=value; domain=.example.com; path=/三、iframe
DOM,典型的例子就是 iframe窗口 和window.open 打开的窗口。他们与父窗口无法通信。比如:父窗口运行一下命令,如果iframe不是同源的,就会报错。
document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.contentWindow是返回当前iframe对象的window对象,可以通过window对象访问iframe内部的文档和dom反之亦然,子窗口获取父窗口的dom也会报错。
解决办法:
document.domain 属性,规避同源策略,拿到dom。- 片段标识符(fragment identifier)
- window.name
- 跨文档通信API
片段标识符,指的是链接url的hash部分
例如:"https://www.example.com/blog/2016/04/same-origin-policy.html#name=1"的 name=1
部分,只改变hash部分,页面不会刷新。
hash部分的变化,同时页面不会刷新。let src = originUrl + '#' + data
document.getElementById("myIFrame").src = srcwindow.onhashchange = () => {
console.log(location.hash)
}同样的,子窗口也可以设置父窗口的hash部分。
parent.location.href = target + '#' + datawindow.name 指的是当前窗口的name属性。一个窗口对应着一个name属性,也就是说当打开
一个新的窗口时,与上个窗口的name属性就无关了。
// 设置属性
window.name = data
// 获取属性
console.log(document.getElementById("myIFrame").contentWindow.name)跨文档通信API(cross-document messaging)
var data = window.open('https://example2.com', 'title')
data.postMessage('hello', 'https://example2.com')是:"https://www.example.com",即协议+ 域名+端口,也可以设置为 *
message 事件,监听对方消息。window.addEventListener('message', (e) => {
console.log(e.data)
})
e,有三个属性:- e.source:发送消息的窗口
- e.origin:接收消息的窗口
- e.data:消息内容
通过window.postMessage,也可以读写其他窗口的LocalStorage。
四、AJAX
- JSONP
- CORS
- WebSocket
最大的特点是:兼容老式浏览器,服务器改造小。只能处理get请求。
JSONP的原理就是利用<script>的标签没有跨域限制的漏洞。通过<script>标签指向一个需要访问的地址并提供一个回调函数来接收数据。或者<img>标签关键点:
- 服务端返回的数据不是
JSON,而是JavaScript,也就是说contentType是text/javascript,内容格式为callbackFunction(JSON) callbackFunction需要注册到window对象上,因为script加载后的执行作用域是window作用域- 需要考虑同时多个
JSONP请求的情况,callbackFunction挂在window上的属性名需要唯一 - 请求结束需要移除本次请求产生的
script标签和window上的回调函数
jsonp({
url: '',
data: {
key: 'value'
},
callback: (data) => {
console.log(data)
}
})function jsonp ({url, data, callback}) {
// 获取head标签
const container = document.getElementsByTagName('head')[0];
// 动态生成回调函数名
const cbName = `jsonp_${new Date().getTime()}`
// 创建script标签
const dom = document.createElement('script')
// 设置script标签的src属性
script.src = `${url}?${objectToQuery(data)}&callback=${cbName}`
script.type = 'text/javascript'
// 插入script标签
container.appendChild(dom)
// 在window上注册回调函数
window[cnName] = function (res) {
callback && callback(res)
// 执行完清除
container.removeChild(dom)
delete window[cbName]
}
// 错误兼容处理
dom.onerror = () => {
window[cnName] = () => {
callback && callback('some error')
container.removeChild(dom)
delete window[cbName]
}
}
}
// 拼接参数
function objectToQuery (obj) {
const arr = [];
for ( var i in o) {
arr.push(encodeURIComponent(i)+ '=' +encodeURIComponent(o[i]));
}
return arr.join('&');
}是一种通信协议,使用ws:// 和 wss:// 作为协议前缀,因为此协议不实行同源策略,只要服务器支持,即可进行跨源通信。
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
Cross-Origin-ResourceSharing,跨域资源共享。跨域请求的终极解决方案。- 整个通信过程都是浏览器自动完成。
- 浏览器一旦发现
AJAX请求跨域后,就会自动添加一些附加的头信息,有时还会多一次附加的请求。 - 只要服务端支持
CORS,就可以跨域
浏览器将CORS请求分为:简单请求和非简单请求
- 请求方法为以下三种之一:
- get
- post
- head
- 头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain
只要不同时满足以上的条件,即为非简单请求。
- 浏览器发现本次请求属于简单请求,自动在头部信息添加
origin字段 - 服务端根据
origin字段,判断是否同意本次请求 - 在许可范围内,响应头会新增几个头信息。
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8- 不在许可范围内,返回一个正常的
HTTP回应 - 浏览器判断响应头中有没有
Access-Control-Allow-Origin字段,没有的话报错,被XMLHttpRequest的onerror回调函数捕获。这种错误是无法通过状态码识别,因为HTTP回应的状态码可能是200
Access-Control-Allow-Origin:该字段是必须的,要么是请求的origin的字段值,要么是 * 表示允许任意域名访问。
CORS请求默认不发送Cookie和HTTP认证信息。
Access-Control-Allow-Credentials字段。Access-Control-Allow-Credentials:truewithCredentials属性let xhr = new XMLHttpRequest();
xhr.withCredentials = trueAccess-Control-Allow-Origin 就不能设置为 *,必须指定与请求源一致的域名。常用情况:请求方法是PUT或者DELETE,或者 Content-Type的字段类型是application/json。
- 非简单请求在发送
cors请求之前会发送一次OPTIONS请求,称为“预检”请求。 - 浏览器先询问服务器,当前请求的域名是否在白名单中,以及可以使用哪些
HTTP动词和头信息字段 - 得到肯定回复,浏览器才会发出正式的
XMLHttpRequest请求
origin:表示请求源
Access-Control-Request-Method:该字段是必须的,表示请求方法
- 服务端收到预检请求后,检查以上三个字段,确认允许跨源请求,就可做出回应
- 如果否定了跨源请求,服务端返回一个正常的
HTTP,但是没有任何相关CORS的响应头字段。浏览器抛出错误,被XMLHttpRequest 的 onerror回调函数捕获 - 如果同意了跨源请求,服务端会在请求头中添加一些字段
Access-Control-Allow-Methods:GET,POST,PUT。该字段是必需的,表示服务端支持的所有的请求的方法
Access-Control-Request-Headers字段,那响应头中也必需也有该字段,表示服务端支持的所有的头信息的字段Access-Control-Allow-Credentials: 该字段是非必须的,表示是否允许发送cookie。默认情况下cookie不包含在cors的请求中
JSONP只支持get请求,但是兼容老式浏览器CORS支持所有请求,但是不兼容ie10以下
跨域资源共享 CORS 详解(www.ruanyifeng.com/blog/2016/0…)
面试中如何实现一个高质量的JSONP(juejin.cn/post/684490…)
浏览器同源政策及其规避方法(www.ruanyifeng.com/blog/2016/0…)