一、跨域如何产生的
在介绍跨域之前,我们先来了解一下浏览器的同源策略。
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
源(origin):协议 + 域名 + 端口号
同源:相同的协议、域名、端口号。
下表给出了相对 http://store.company.com/dir/page.html
同源检测的示例:
只要上面任意一个条件不符合就会产生跨域问题,所以跨域和浏览器的同源策略有很大关系。
二、跨域有什么限制
对于不同源的常见的限制:
- Cookie、LocalStorage、IndexDB无法获取
- DOM无法获得
- Ajax、Fetch请求无法响应(服务端可以响应,但是被浏览器拒绝了)
要注意的是,同源策略只有浏览器才有,跟服务器没有任何关系。
三、解决方案
3.1 JSONP
很简单,就是利用<script>
标签没有跨域限制的“漏洞”(历史遗迹啊)来达到与第三方通讯的目的。当需要通讯时,本站脚本创建一个<script>
元素,地址指向第三方的API网址,形如:
<script src="http://www.example.net/api?param1=1¶m2=2"></script>
并提供一个回调函数来接收数据(函数名可约定,或通过地址参数传递)。
第三方产生的响应为json数据的包装(故称之为jsonp,即json padding),形如:
callback({
"name":"hax","gender":"Male"})
这样浏览器会调用callback函数,并传递解析后json对象作为参数。本站脚本可在callback函数里处理所传入的数据。
补充:“历史遗迹”的意思就是,如果在今天重新设计的话,也许就不会允许这样简单的跨域了嘿,比如可能像XHR一样按照CORS规范要求服务器发送特定的http头。
以上是引用贺师俊老师在知乎的回答。
下面我们来实现一个简单的JSONP请求。我们以百度联想词搜索接口为例。
首先,我们把资源(demo.html
)部署到本地端口为8888
的Node服务器上:
server.js
const http = require('http')
const fs = require('fs')
http.createServer((req, res) => {
let html = fs.readFileSync('demo.html', 'utf-8')
res.writeHead(200, {
'Content-Type': 'text/html'
})
res.end(html)
}).listen(8888, () => {
console.log('服务器已经启动在8888...')
})
然后在demo.html
中发起HTTP请求:
demo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<input type="text" placeholder="请输入关键字" />
<script>
let input = document.querySelector('input')
let body = document.body || document.documentElement
// 监听输入事件,发起jsonp请求
input.addEventListener('input',jsonp)
function jsonp() {
let keyword = this.value || ''
let callback = 'show'
let url = `https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=${
keyword}&cb=${
callback}`
let script = document.createElement('script')
script.src = url
script.id = 'jsonp'
body.appendChild(script)
}
function show({
s }) {
// 对数据进行处理
console.log(s)
// 删除script标签
body.removeChild(document.querySelector('#jsonp'))
}
</script>
</body>
</html>
这样我们就可以得到来自百度发过来的数据:
这是很久之前使用的方法,现在基本上都不用了,因为它有两个常见的缺点:
- 只支持GET请求
- 可能导致XSS攻击
我们可以看到Chrome浏览器也给出了警告,所以通过JSONP这种方式会带来一定的安全隐患,Chrome在将来发布的版本中不会再支持了。
注意:上面的例子只是为了简单说明JSONP怎么工作的,具体实现不会这么简单。下面我们来说说CORS
。
3.2 CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。目前这种方式被广泛使用。这种机制使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。
CORS需要服务端和浏览器同时配合,但服务端是主要的,需要在服务端设置一些头部字段就能完成跨域,而浏览器会自己去解析头部字段来完成通信过程。目前浏览器除了IE10以下版本都已经支持了CORS标准。
CORS应用于以下几种方式的跨域请求:
- 由
XMLHttpRequest
或Fetch
发起的HTTP请求 - Web 字体 (CSS 中通过
@font-face
使用跨域字体资源) WebGL
贴图- 使用
drawImage
将 Images/video 画面绘制到 canvas
我们针对第一种情况详细来说,其余情况我们不是很常用,所以本篇文章不会讲解其余三种类型的跨域请求,有兴趣的小伙伴可以去MDN查阅。
CORS的工作原理:当浏览器发起HTTP实际请求之前,CORS机制首先判断请求的类型,如果是复杂类型
请求,则会通过OPTIONS
方法向服务器发起