7

本文源自一次内部关于跨域的讨论分享的总结

理解跨域的重点在于:了解跨域产生的场景、原理

跨域问题只在浏览器客户端环境下出现,是由于浏览器出于安全考虑设置的同源策略引起,所以解决跨域问题一般采用的思路有两种:
a. 绕过浏览器的同源策略约束
b. 遵循新的跨域规范

浏览器同源策略

同源策略:不同域的客户端在没明确授权的情况下,不能读写对方的资源
同域:协议、域名、端口号均相同
比如:

http://abc.com  vs  https://abc.com
http://ab.abc.com  vs  http://a.abc.com 
127.0.0.1:3000  vs  127.0.0.1:4000

哪些行为会受到浏览器同源策略的约束呢?

  • ajax请求 XMLHttpRequest
    注意:浏览器并不会限制跨域请求的发出,即服务端再未做其他限制的情况下仍可收到跨域请求,浏览器只是限制了ajax请求返回信息的读取、cookie写入等操作
    扩展:服务端如何限制仅接收指定域名请求? refer?
  • fetch
  • 非同域窗体之间(比如iframe引入的情况的父子窗体)不能直接进行数据通讯或共享
  • Web 字体 (CSS 中通过 @font-face 使用跨域字体资源)
  • WebGL 贴图
  • 使用 drawImage 将 Images/video 画面绘制到 canvas

等等

哪些标签可以加载非同源资源?
script img link iframe
跨域方案的思路a也就是基于以上几种标签

JSONP -- 思路满分

json with padding

基本原理:利用script标签的src属性允许加载非同源资源,加载后js解析器将执行资源代码,目标服务器在数据外层包裹一个客户端已经定义好的函数并返回

服务端接口返回一段可执行js脚本
"hello browser"   =>   callback("hello browser")

前后端交互流程:

jsonp前后端交互流程

  • 首先在浏览器端注册一个全局函数callback。
  • 浏览器端创建一个script标签,将src指向目标服务端资源地址,并带上callback参数,告诉服务端回调函数名称,将此script标签插入到Dom中。
  • 服务器生成返回json数据 ,获取请求中的callback函数名参数,再将json数据以参数填充的方式,和函数名参数拼接:callback+’(‘+json+’)’,最后将拼接完成的数据返回。
  • 浏览器端,解析script标签,并执行返回的javascript 文档,此时数据作为参数,传入到了客户端预先定义好的callback 函数里(动态执行回调函数)。

优缺点对比

优:

  • 兼容性极佳,在不支持XMLHttpRequest的浏览器中也可以用于模拟异步请求

缺:

  • 只允许get请求: jsonp跨域原理是通过script标签加载跨域资源,不可能支持post请求
  • 存在一定安全隐患: 本质上来说,jsonp是一种脚本注入(Script Injection)行为
  • 需要服务端配合支持:如果一个接口同时需要适配jsonp和正常请求,需要特殊处理没有错误处理支持
  • 如果动态脚本插入有效,就执行调用;如果无效,就静默失败:不能从服务器捕捉到 404 错误,也不能取消或重新开始请求。(自行设置定时器,超时后没有进入回调即判定为请求失败 )

栗子 ——淘宝和天猫通过jsonp跨域共享cookie

在淘宝(www.taobao.com)登录后,切换到天猫(www.tmall.com),会看到顶栏已经有登录用户信息。打开控制台,刷新tmall页面,可以看到如下jsonp请求,其中第一个即为获取到登录信息的关键请求:
获取共享登录cookie信息的jsonp请求

打开该请求的Response内容:
response内容
这段返回内容(本质上是js代码)到达客户端后,将会被解析执行

利用消息通信机制实现的跨域请求

@Update :不管是post message、window.name共享、location.hash共享,这类方案的原理都是依赖消息通信机制实现的,故更改标题

以个人经常用到的Frame代理为例

基本原理:在目标服务器放置一个代理文件(proxy_frame.html),通过加载该代理文件和服务端进行数据交互(同域请求),返回数据通过消息通讯(如post message)返回给上层应用以实现跨域数据交互
a.b.com域页面
加载跨域iframe文件
实际上是利用窗体之间通讯方式 将跨域请求转化为同域请求

前后端交互流程:(以nej框架的实现为例)

iframe跨域前后端交互流程

  • 假设a.b.com的应用需要向x.y.com的服务器取数据,首先在x.y.com域服务器上预先放置代理文件,并以iframe载入该代理文件
  • 服务器端返回带配置的代理文件
  • 代理文件载入完成后,a.b.com域的应用将要发送的请求指令通过消息通信方式传递给代理文件
  • 代理文件验证a.b.com是否在预先配置的白名单中,如果不在则异常返回,否则直接发送请求至x.y.com域服务器
  • 服务器返回数据至代理文件
  • 代理文件通过消息通讯机制将请求结果返回给a.b.com域的应用

窗体间消息通信方式:

针对高版本浏览器:HTML5 Web Message
针对Trident引擎低版本浏览器(ie6-7):window.name代理(复杂结构需要stringify,启用队列修改)
image.png
image.png

优缺点对比:

优:

  • 服务端无需额外支持:仅需要放置代理用的html文件即可
  • 对请求method完整支持:get、post、put等等
  • 兼容性较好:需要一些兼容处理(主要针对消息通信方式)

缺:

  • 首次请求存在延时:由于首次发起请求时需要载入代理文件,在载入代理文件之前的所有请求都会存在一定的延时
  • 低版本浏览器并发请求延时较大:由于低版本浏览器受限于消息通讯机制,对于并发量大的请求返回时可能存在较大延时

Cross-Origin Resource Sharing规范 -- 简称CORS

为了解决跨域问题出现的标准规范
通过增加一系列请求头和响应头规范安全地进行跨站数据传输,它要求浏览器必须能支持CORS规范定义的请求头和策略执行,并且服务端需要解析这些新的请求头并按照策略返回对应的响应头和请求的资源

分为以下三种请求场景:
cors规范三种请求场景

附录:对 CORS 安全的首部字段集合

相关请求头和响应头(主要)

响应头:
响应头说明
请求头:
请求头说明

如何使用?

直接使用ajax(根据浏览器版本选择XHR或XDR对象)或fetch即可,客户端只需按规范设置请求头
服务端按规范识别并返回对应响应头,还可以对请求域名进行过滤处理
比如使用Nginx配置:(待补充)

还是看几个栗子:

客户端发起一个get请求,观察一次成功简单请求的请求头与响应头:
image.png

客户端发起一个post请求,并设置Content-Type为application/xml,观察一次成功预检+正式请求的请求头与响应头:
Options预检
Post正式

客户端发起一个post请求,并设置设置特殊标志位 withCredentials为true ,观察一次成功预检+正式请求的请求头与响应头:
Options预检
Post正式

优缺点:

优:

  • 标准 规范 安全
  • 对请求method完整支持:get、post、put等等
  • 完整的错误处理:使用XMLHttpRequest对象或FetchAPI发送请求

缺:

  • 兼容程度较低:需要高版本浏览器支持,Trident引擎的浏览器需要IE10以上才完整支持

IE6-7 完全不支持CORS
IE8-9 仅支持不带凭证的CORS跨域请求

其他

WebSocket

建立socket长连接,需要验证,本质上可以视为安全,不存在跨域限制
由于资源消耗较大,除了一些特殊场景,一般不使用

服务端反向代理

将本域服务端配置成 需要跨域获取的资源的 反向代理服务器
比如:使用Nginx配置请求转发:proxy_pass
反向代理

FLASH代理

与frame代理模式类似,请求通过Flash来发送(proxy_flash.swf放置在同源站),利用Flash的策略文件crossdomain.xml来控制资源的共享权限,获取目标服务器请求返回数据
---相当于把iframe改成flash

还有例如 img ping 等等等等

总结

跨域永远是无奈之举,常规情况下不应该出现

针对少量需要跨域Get请求的场景 : JSONP仍是不错的选择
针对整站大量跨域请求 :
—— 兼容性要求高: iFrame代理跨域/服务端反向代理

—— IE10以上兼容支持: CORS规范

检测浏览器支持: 高版本使用CORS规范,低版本自动降级使用iFrame代理


饭饭之辈
211 声望11 粉丝

米粒是个大傻帽


引用和评论

1 篇内容引用
0 条评论