浏览器跨域同源梳理

一、同源策略

  • 协议相同
  • 域名相同
  • 端口相同

限制范围

  1. Cookie、LocalStorage 和 IndexDB 无法读取。
  2. DOM 无法获得。
  3. AJAX 请求不能发送。

iframe

如果两个网页不同源,则无法拿到对方的 DOM,可以设置 document.domain(方案同 cookie)

二、(不同源)跨域窗口通信

1. 片段标识符

通过 URL 后面添加#,页面不会刷新(vue-router 的 hash 模式同)。

父窗口把信息写到子窗口 url 中,子窗口监听 hashchange 事件,子窗口也同样可以改变父窗口的片段标识符

2. window.name

设置之后跳转到其它页面,这个属性不会变化。因此可以如下操作

  • 子窗口 window.name=’test’;
  • 子窗口跳到一个和父窗口同域的网址
  • 主窗口监听到子窗口的 window.name 变化,获取子窗口设置的数据
1
var data = document.getElementById("myFrame").contentWindow.name

3. postMessage 通信

HTML5 的跨文档通信 API

父窗口向子窗口发送发送信息

1
2
3
4
$("#postM").click(function (evt) {
var popup = window.open("http://localhost:8000/public/index.html", "title")
popup.postMessage("Hello World!", "http://localhost:8000/")
})

子窗口监听

1
2
3
4
5
6
7
window.addEventListener(
"message",
(e) => {
alert(e.data)
},
false
)

4. LocalStorage 读取(使用 postMessage)

原理同上,父窗口发送数据,子窗口接收到后,将其存入 localStorage(单点登录?)

三、AJAX 请求

1. jsonp

只能 GET 请求

  • 创建一个 script 标签,这个 script 标签的 src 就是请求的地址;
  • 这个 script 标签插入到 DOM 中,浏览器就根据 src 地址访问服务器资源
  • 返回的资源是一个文本,但是因为是在 script 标签中,浏览器会执行它
  • 而这个文本恰好是函数调用的形式,即函数名(数据),浏览器会把它当作 JS 代码来执行即调用这个函数
  • 只要提前约定好这个函数名,并且这个函数存在于 window 对象中,就可以把数据传递给处理函数。

2. WebSocket

WebSocket 不遵循同源策略,只要服务端验证通过,就可以正常通信

3. CORS

跨域请求相对比较完善的方案
‘Access-Control-Allow-Origin’

简单请求

需要同时满足两大条件

请求方法

  • GET
  • POST
  • HEAD

HTTP 请求头没有额外的,而且允许的 Content-Type

  • text/plain
  • multipart/form-data
  • application/x-www-form-ulencoded

简单请求会自动在头信息中加一个 Origin 字段

非简单请求

规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求。

服务端在 HTTP header 中加入允许请求的方法和 Content-Type 后,其他指定的方法和 Content-Type 就可以成功请求了

  • ‘Access-Control-Allow-Headers’: ‘允许 Content-Type’
  • ‘Access-Control-Allow-Methods’: ‘允许的请求方法’
  • ‘Access-Control-Max-Age’: ‘预请求允许其他方法和类型传输的时间’

4. 代理

四、cookie 相关

这里说明一点,cookie 不能跨域(但是如果端口相同,可以直接共用),即相同域名下不同端口,cookie 是可以共用的。

跨域请求需要在客户端添加

1
xhr.withCredentials = true

但是这里仅仅是服务端能接收到,但是由于同源策略,会被挡住。同时还要在服务端设置

1
2
'Access-Control-Allow-Origin': 'http://localhost:8080',
'Access-Control-Allow-Credentials': true

注意这里 Origin 不能设置通配符,只能具体指定。

只能指定当前域名,或者其父域名,比如当前 a.domain.com
则 domain 可以指定为 a.domain.com 或者 domain.com 其它一律失效。
因此,利用 cookie-session 实现单点登录,在同一个父域下,非常好实现,将其设置为顶域

举例 sso.domain.com a.domain.com b.domain.com
在 sso.domain.com 下登陆,然后将 domain 设置为 domain.com,则另外两个子域就都能获取到。
同时还有一个 session 一致性的问题,详见 整理-cookie-session

  • 单台服务器保存
  • nginx 配 ip_hash(一种负载均衡方案)
  • 存数据库里,每次查表
  • redis

子路径可以访问父路径,但是父路径不能拿到子路径的 cookie,比如

  • ‘/‘: 则所有路径都能获取
  • ‘/test’: 则只有 test 路径下的能获取,根路径无法获取

HttpOnly

  • 设置后无法通过在 js 中使用 document.cookie 访问
  • 保障安全,防止攻击者盗用用户 cookie

和 SessionStorage 不一样,当浏览器关闭后,cookie 才失效,而非窗口关闭

五、localStorage 跨域