同源策略
什么是同源
在了解跨域这个概念之前首先要知道的是何为同源策略。所谓的同源是一种安全机制,为了预防某些恶意行为(例如 Cookie 窃取等),浏览器限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。而满足同源要具备三方面:协议相同、域名相同、端口相同。
以下是对于http://domain.com/dir/index.html
(默认端口 80)来进行同源判断:
http://domain.com/dir2/info.html
(同源)https://domain.com/dir/index.html
(非同源,协议不相同)http://www.domain.com/dir/index.html
(非同源,域名不同)http://domain.com:233/dir/index.html
(非同源,端口不同)
什么地方有要求同源
- Ajax 通信
- Cookie
- LocalStorage
- IndexDB
- DOM 的操作
跨域资源共享
同源策略对于用户信息安全是必不可少的,但是实现合理的跨域请求也是很重要的,于是 W3C 就定了一个叫CORS(Cross-Origin Resource Sharing)的草案,也就是跨域资源共享。其基本思想就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功或是失败。
CORS 的简单请求原理
实现 CORS 需要浏览器与服务器的同时支持。例如发送一个简单的GET
或POST
请求,浏览器会为其添加一个Origin
的头,其包含页面的源信息(协议、域名和端口),如:
Origin: http://domain.com
若服务器认为该请求可接受,就在Access-Control-Allow-Origin
头部中回发相同的源信息(我们有时调用的公共 API,大部分都是将该头部设为*
,但是它们都不发送 Cookie)。要注意的是请求和响应都不包含 Cookie 信息。
以上都为简单请求,对于非简单请求,CORS 通过一种叫做 Preflighted Requests 的透明服务器验证机制支持开发者使用自定义头部信息或者 GET 和 POST 之外的方法,不过代价是在正式通信前增加一次 HTTP 请求,这里就不详细描述了。
浏览器对 CORS 的实现
现代浏览器都对 CORS 提供了原生支持(IE8、9 是利用XDomainRequest
,不过已废弃),无需编写额外代码即可触发简单的跨域行为,因为浏览器会自动帮你添加一些头部信息,但是有以下限制:
- 不可使用
setRequestHeader()
设置自定义头部。 - 默认情况下不能请求 Cookie 等凭据,除非服务器在响应头中将
Access-Control-Allow-Credentials
设为true
。 - 调用
getAllResponseHeaders()
会返回空字符串。
图像 Ping
该跨域技术主要是利用<img>
标签设置src
属性(请求地址通常都带有查询字符串),然后监听该<img>
的onload
或onerror
事件来判断请求是否成功。响应的内容通常是一张 1 像素的图片或者204
响应。
图片 Ping 有两个缺点:
- 因为是通过
<img>
标签实现,所以只支持GET
请求。 - 无法访问服务器响应脚本,只能用于在浏览器与服务器之间进行单向通行。
由于以上特点,图片 Ping 方法常用于跟踪用户点击页面或动态广告的曝光次数。
JSONP
JSONP 是 JSON with padding 的简写,其主要是利用动态创建<script>
标签向服务器发送 GET 请求,服务器收到请求后将数据放在一个指定名字的回调函数中并传送回来。接下来看一下简单示例:
浏览器:
//对创建标签行为进行封装
function addScriptTag(src) {
var script = document.createElement('script')
script.setAttribute("type","text/javascript")
script.src = src
document.body.appendChild(script)
}
//当浏览器加载完毕时向服务器发送请求
window.onload = function () {
addScriptTag('http://domain.com/data?callback=getdata')
}
//服务器收到上面的请求后,将数据放在回调函数的参数(data)中返回
function getdata(data) {
console.log(data)
}
jQuery 也有对 JSONP 的封装,有兴趣的可以了解一下(文章尾部链接)
服务器
//服务器获取参数名后,将回调函数和参数拼接为字符串返回
response.send(
`${query.callback}({
"name": "Hello"
})`
)
其他的跨域方法
跨域方法其实还有不少,这里先总结这么多,往后若时间允许的话就更新 😬
- HTML5 的 postMessage
- WebSocket(当然协议就不一样了)
- document.domain(iframe)
- location.hash(iframe)
- window.name
- nginx 反向代理
相关参考
- 《JavaScript 高级程序设计(第三版)》
- 阮一峰:浏览器同源政策及其规避方法
- 阮一峰:跨域资源共享 CORS 详解
- MDN:HTTP 访问控制(CORS)
- Working with JSONP