ChuChencheng / note

菜鸡零碎知识笔记
Creative Commons Zero v1.0 Universal
3 stars 0 forks source link

跨域 #33

Open ChuChencheng opened 4 years ago

ChuChencheng commented 4 years ago

为什么会有跨域问题

为什么会有跨域?因为浏览器有个 同源策略

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

简单来说,同源策略限制了不同源之间资源的访问。假设你部署了一个应用 A ,别人在另一个域名上部署了应用 B ,那么,在同源策略的限制下,应用 B 就没法直接通过 ajax 请求应用 A 的东西。

同源的定义

如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的源。

例如,对于 http://www.aaa.com 来说:

http://www.aaa.com/bbb   // 同源,只有路径不同
https://www.aaa.com   // 不同源,协议不同
http://mail.aaa.com   // 不同源,域名不同
http://www.aaa.com:8080   // 不同源,端口不同

同源策略的限制

同源策略会限制以下情况的跨域访问:

如何允许跨域访问

CORS

跨域资源共享(Cross-Origin Resource Sharing)是一种机制,通过使用额外的 HTTP 头来确认是否可以进行跨域访问

简单请求

当请求满足一定的条件时,不会触发 CORS 预检请求 (即在请求之前再发送一个 OPTIONS 请求),这类请求称为 “简单请求”

满足以下所有条件即可视为 “简单请求” :

  1. 使用以下方法之一: GET, POST, HEAD
  2. HTTP 头信息不超出以下字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type:仅限于 text/plain, multipart/form-data, application/x-www-form-urlencoded 三个值之一
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width

预检请求

预检请求要求首先使用 OPTIONS 方法发起一个预检请求道服务器,以获知服务器是否允许该实际请求,可避免跨域请求对服务器的用户数据产生未知的影响。

当请求满足一下任一条件时,应先发送预检请求:

  1. 使用了 PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH 中的任一方法
  2. 人为设置了 简单请求 头部字段之外其他字段

HTTP 响应头字段

Access-Control-Allow-Origin

语法:

Access-Control-Allow-Origin: <origin> | *

可以指定为通配符 * 表示允许所有来源的跨域访问

或者指定一个特定的域名

Access-Control-Expose-Headers

跨域访问时, XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到一些最基本的响应头,如果需要拿到其他的响应头,需要在服务器端设置响应头的 Access-Control-Expose-Headers 字段,相当于一个白名单,如:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

这样, getResponseHeader() 即可访问到 X-My-Custom-Header, X-Another-Custom-Header 响应头了。

Access-Control-Max-Age

这个响应头表示 预检请求 的结果能被缓存多久

语法:

Access-Control-Max-Age: <delta-seconds>

delta-seconds 表示缓存的秒数

Access-Control-Allow-Credentials

指定当浏览器的 credentials 设置为 true 时,在跨域请求时会带上 Cookies 进行身份验证。如果是简单请求,但没有带上 Access-Control-Allow-Credentials: true ,这时浏览器不会将相应的响应内容返回给请求的发起者,响应会被忽略;如果是 预检请求 ,带上 Access-Control-Allow-Credentials: true 则表示是否可以使用 credentials

Access-Control-Allow-Methods

用于 预检请求 ,指明实际请求所允许使用的 HTTP 方法

语法:

Access-Control-Allow-Methods: <method>[, <method>]*

Access-Control-Allow-Headers

用于 预检请求 ,指明实际请求中允许携带的头部字段

语法:

Access-Control-Allow-Headers: <field-name>[, <field-name>]*

HTTP 请求头字段

Origin

表明 预检请求 或 实际请求 的源站

Access-Control-Request-Method

用于 预检请求 ,表示将实际请求所使用的 HTTP 方法告诉服务器

语法:

Access-Control-Request-Method: <method>

Access-Control-Request-Headers

用于 预检请求 ,表示将实际请求所携带的头部字段告诉服务器

语法:

Access-Control-Request-Headers: <field-name>[, <field-name>]*

JSONP

原理

利用 script 标签没有同源限制的特点

优缺点

优点: 兼容性好,可跨域 缺点: 只支持 get 方法,可能会受到 XSS 攻击

流程

  1. 浏览器端声明一个回调函数,参数为跨域请求后的数据,例如:
window.jsonpCallback = (responseData) => {
  // 处理跨域请求返回的数据
}
  1. 创建一个 script 标签, src 为跨域 API 的地址,将第一步定义好的回调函数名附在地址上,例如 /cross-origin-api/getUser?callback=jsonpCallback
  2. 服务端收到请求后,将返回数据作为参数传入回调中,生成一个调用回调函数的字符串, 'jsonpCallback({ data: 666 })' ,将生成的字符串返回给浏览器
  3. 浏览器得到返回的字符串后,将字符串作为代码执行,也就是会执行第一步声明的回调函数:
// 也就是执行了:
window.jsonpCallback({ data: 666 })

postMessage

postMessage 可以安全地实现跨域通信,一般用在以下场景:

语法如下:

otherWindow.postMessage(message, targetOrigin, [transfer])

在其他窗口中,只要在 window 下监听 message 事件,即可收到消息。

需要注意的是,收到消息时记得要判断一下消息是否来自期望的源,把非预期的源的消息都过滤掉,保证安全。

WebSocket

WebSocket 是一种双向的通信协议,可以跨域使用,在建立连接时使用的是 HTTP 协议,之后的通信就与 HTTP 无关了。

Nginx 代理

同源策略是对浏览器与服务端之间通信的限制,服务端之间并没有这个限制。

那么,只要在中间再搭一层 Nginx ,其服务与浏览器同源,与实际的业务服务端不同源,在浏览器对 Nginx 服务器发起请求时,将请求转发到实际的业务服务器,即可解决跨域问题。

window.name + iframe

location.hash + iframe

document.domain + iframe

参考