跨域

同源策略

当”协议+域名+端口”三者相同时,才能够相互访问资源。保证用户信息的安全,防止恶意的网站窃取数据。

限制

  • 当前域下的 js 脚本不能够访问其他域下的 cookielocalStorageindexDB 等本地存储
  • 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM
  • 当前域下 ajax 无法发送跨域请求。

跨域访问

CORS

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器让运行在一个 origin (domain)上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。

简单请求

简单请求不会触发 CORS 预检请求。需要满足以下条件才能成为简单请求

请求方法
  • HEAD
  • GET
  • POST
HTTP 的头信息

不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
请求过程
  1. 浏览器会直接发出 CORS 请求,它会在请求的头信息中增加一个 Orign 字段,该字段用来说明本次请求来自哪个源(协议+端口+域名)
  2. 服务器会根据 origin 值来决定是否同意这次请求
1
2
3
Access-Control-Allow-Origin: http://api.baidu.com  //  Orign 一致,必须要包含,可以为 *
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值

非简单请求过程

在正式通信之前进行一次 HTTP 查询请求,称为预检请求

请求过程
  1. 使用 OPTIONS 方法向服务端发起预检请求
1
2
Access-Control-Request-Method:该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法。
Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段。
  1. 服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进行判断是否允许跨域
1
2
3
4
5
Access-Control-Allow-Origin: http://api.baidu.com  // 允许跨域的源地址(必须)
Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法(必须)
Access-Control-Allow-Headers: X-Custom-Header // 服务器支持的所有头信息字段(必须)
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000 // 用来指定本次预检请求的有效期,单位为秒
  1. 默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie.
  2. Access-Control-Allow-Credentials 设置为 true
  3. Access-Control-Allow-Origin 设置为非 *

JSONP

利用<script> 标签没有跨域限制,通过<script>标签 src 属性,发送带有 callback 参数的 GET 请求,服务端将接口返回数据拼凑到 callback 函数中,返回给浏览器,浏览器解析执行,从而前端拿到 callback 函数返回的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let count = 0
function jsonp(src, callback) {
count++
const functionName = 'jsonpCallback' + count
// 回调执行函数
window[functionName] = function (res) {
callback && callback(res)
}
const script = document.createElement('script')
script.type = 'text/javascript'
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
const realSrc = src.includes('?')
? src + '&callback=' + functionName
: src + '?callback=' + functionName
script.src = realSrc
document.head.appendChild(script)
}

// 服务端实现
app.get('/test', (req, res) => {
const { callback } = req.query
res.send(`${callback}({ test: "a" })`)
})

通过 script 标签的形式获取到的数据会被自动执行,所以通过返回回调函数调用的方式自动执行代码

缺点

  • 具有局限性, 仅支持 get 方法
  • 不安全,可能会遭受 XSS 攻击

postMessage

特点

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的 iframe 消息传递
  • 上面三个场景的跨域数据传递

服务代理

服务端转发请求

document.domain + iframe

此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。

location.hash + iframe

a 欲与 b 跨域相互通信,通过中间页 c 来实现。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。

说明

  • A 与 B 不同域,A 向 B 传递消息时通过修改 B 页面的 hash 值,B 监听 hash 值的变化获取数据
  • A 与 C 同域,B 与 A 通信时通过修改 C 页面的 hash 值,C 通过各种手段通知 A hash 值的辩护

window.name + iframe

window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

WebSocket

通过 WebSocket 服务端中转通信

参考