聊聊常见的跨域问题

栏目: Json · 发布时间: 7年前

内容简介:跨域是前端工程中一个很常见的问题啦. 今天就来聊一聊跨域, 以及常见的处理方式和原理.跨域实际上源于浏览器对javascript的一种安全限制(也被称之为同源策略). 默认情况下, 我们只能访问同一协议、同一域名、同一端口下的资源. 如今都是提倡前后分离的情况下, 前端更是需要调用大量后台接口的场景, 因此解决跨域的问题就摆在了面前.产生跨域的原因我们主要归为这两点:
聊聊常见的跨域问题

跨域是前端工程中一个很常见的问题啦. 今天就来聊一聊跨域, 以及常见的处理方式和原理.

什么是跨域?

跨域实际上源于浏览器对javascript的一种安全限制(也被称之为同源策略). 默认情况下, 我们只能访问同一协议、同一域名、同一端口下的资源. 如今都是提倡前后分离的情况下, 前端更是需要调用大量后台接口的场景, 因此解决跨域的问题就摆在了面前.

产生跨域的原因

产生跨域的原因我们主要归为这两点:

  1. 浏览器安全限制(不能读取不同域、端口、协议下的内容)
  2. 使用的是XHR(XMLHttpRequest)请求

同源策略这个原因是众所周知了, 它作为一个安全策略, 的确有效预防了某些安全上的问题. 但同时又阻止了大量适合使用的跨域请求. 而 XHR 也受同源策略影响: 浏览器不允许 javascript 查找跨域文档的内容. 使用 XHR, 文档内容都是通过 responseText 属性暴露, 因此同源策略不允许 XHR 进行跨域请求.

解决跨域

跨域很多情况下都需要后端的配合, 因此主要先来谈谈前端的跨域方案.

JSONP

前文我们说过, 产生跨域的原因之一XHR请求, 但是 script 发出的请求类型(type)并不是 xhr , 因此可以解决跨域的问题.

JSONP 由回调函数和数据组成的, 实现方式就是动态创建一个 <script> 标签, 然后设置 src 属性指向的跨域的URL(包涵请求参数). 来向服务端请求数据.

比如我们要查询小明的信息, 这时我们得知它的 userID 为 1150, 同时我们都知道GET请求可以通过url进行传参, 因此我们向服务器发起请求:

var script = document.createElement('script')
script.src = "https://www.example.com/users?user_id=1150"
document.body.appendChild(script)

在插入 <script> 标签到 <body> 后, 浏览器立马就去请求服务器的资源. 值得注意的是, 使用 jsonp 也需要服务端的配合. 因此必须通过某种方式来告知服务端, 我们正在通过 <script> 标签调用请求, 必须返回一个JSONP响应, 而不应该是普通JSON响应.

至于什么叫 jsonp 响应呢? 这里其实很好理解.. 假设后端发回来的是 json 格式的数据, 我们也用不了呀, 数据还是数据, 不会做任何变化.. 为了让浏览器可以在 <script> 标签里直接使用, 我们需要让服务端返回一段js代码 —— 用函数包装的json的形式(这也jsonp中”P(padding)”的含义). 这个函数名前后端可约定. 如下:

// 服务端返回 js 代码到 <script>里
userData({'naem':'小明','id':1150,'level':'中等'})

// 前端定义函数
function userData(data){
  // 当 jsonp 请求成功后, 将json传入函数并调用, 我们拿到 json 后就可以做一些其他的事
}

我们来拿B站为例. 打开chrome下的network, 上图就是 jsonp 的应用, 服务端返回的js脚本. 下图可以发现, 我们发出去的请求类型是 script , 验证了前文所说的 <script> 不受同源策略影响的.

聊聊常见的跨域问题

目前主流的类库都对 jsonp 进行了封装, 如 JQuerygetJSONajax , 这里就不深入讲解了. 最后对 jsonp 总结一下:

jsonp 实际上是一个非正式传输协议, 或者说是一种”投机取巧”的方式. 我们可以利用 <script> 的特性从而进行数据交互解决跨域的问题. 相对来说, 它也有一定的局限性: 只能应用在GET请求上, 除此之外还有安全性的问题 —— 只能用在我们信任的服务端, 因为你不能保证对方未来会给你传些什么…

跨域资源共享(CORS)

概述

说完了”不正规”的 jsonp , 紧接着我们再说说原生的 CORS 规范. 我们先来看看官方的定义:

CORS(Cross-origin resource Sharing, 跨资源共享), 定义了访问跨域资源时, 浏览器和服务器应该如何沟通. 其背后主要思想就是 使用自定义的HTTP头部 来让浏览器与服务器进行沟通, 从而决定请求或相应是否成功, 还是应该失败.

目前主流的浏览器都已经对CORS有着良好的支持, 而IE8 ~ 9则还需要使用专用的 XDomainRequest 这里我们抛开不谈.

聊聊常见的跨域问题

这个功能实际上是由浏览器自动完成的, 我们并不需要做什么额外的工作. 对于开发者来说, 也就需要了解一些安全细节的问题, 这一点我们放在后面讲.

两种请求

浏览器发送CORS请求时, 会将请求分为 简单请求非简单请求 .

在我们日常工作中, 常用的 简单请求 可以将其归为以下几点:

  • 使用的方法(Methods)为 HEADGETPOST
  • 请求头无自定义头
  • Content-Type 只能是以下几种
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded

非简单请求:

  • PUT , Delete 方法的 ajax 请求
  • 发送 JSON 格式的 ajax 请求(比如post数据)
  • 带自定义头的 ajax 请求

如果是简单请求, 则会 先执行, 后判断 。执行的过程大致如下:

浏览器发起请求检测到是 CORS 请求, 然后添加一个 origin 字段(其中包含页面源信息: 协议、域名、端口) => 服务端收到后作相应的处理(对比 origin , 服务端判断这个源是否接受)返回结果给浏览器 => 浏览器检查响应头是否允许跨域信息 => 允许, 那就当做没事发生. 不允许, 浏览器抛出相应的错误信息(值得一提的是, 这时状态码也还有可能是200).

非简单请求执行顺序又有些不同. 在发生 CORS 请求时, 浏览器预先发送一个 option 请求. 浏览器这种行为被称之为 预检请求(Preflighted request) . 其中包含如下的请求头:

  • origin: 同上,包含页面源信息.
  • Access-Control-Request-Methods: 请求方法
  • Access-Control-Request-Header: 自定义头部信息, 多个头部以逗号分隔(可选, 看请求时有没有定义请求头)

举个栗子, 我们用JQuery发送一段JSON格式的请求做演示:

var result;

$.ajax({
  type : "post",
  url: "https://www.example/api/rank",
  contentType : "application/json;charset=utf-8",
  data: JSON.stringify({name: "something"}),
  success: function(json){
    result = json;
  }
});

这时请求头(Request Headers)信息如下:

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Access-Control-Request-Header: content-type
Access-Control-Request-Methods: POST
Connection:keep-alive
Host: https://www.example.com
origin: localhost:8080
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1

服务端接收到预检请求后, 判断是否允许这种类型的请求. 在响应头(Response Header)上返回如下头部与浏览器进行交流:

  • Access-Control-Allow-Origin: 服务端允许的源信息
  • Access-Control-Allow-Methods: 服务端允许的方法, 多个方法可以使用顿号分隔
  • Access-Control-Allow-Headrs: 服务端允许的头部, 多个头部可以使用顿号分隔

当预检请求被通过后, 我们原本想要发送的请求才会发送出去.

另外, 细心的你或许已经注意到了, 非简单请求这一来一回需要发送两次请求, 如果频率高的情况下岂不是很费性能又影响效率? 所幸的是HTTP协议新增(IE10+)了一个响应头用于缓存预检请求. 服务端在响应头添加如下字段:

Access-Control-Max-Age: 3600

这个响应头表示这个预检请求可以缓存多长时间, 单位为秒. 这里3600s = 1h, 也就是说一个小时内可以不用再发预检命令了.

带 cookie 的跨域请求

默认情况下, 跨域请求是不带上 cookie 的. 前端需要将 withCredentials 属性设置为 true , 同时还需要服务端设置 Access-Control-Allow-Credentialstrue 启动 cookie. 如果在发送 cookie 的时候, 浏览器检测到服务端响应头没有这个头部, 那么就会在控制台抛出一个错误.

另外, 还有一个值得注意的是. 服务端响应头设置了 Access-Control-Allow-Origin: * 的话, 是不能满足带 cookie 的跨域请求的. 因此有这种场景不能使用通配符, 需要全匹配字段.

CORS 总结

简单总结一下 CORS. CORS的出现也是为了解决跨域的问题. 只不过和 JSONP 不同, 它是纳入规范的一部分, 它几乎支持所有的类型的HTTP请求(JSONP只能使用GET). 唯一美中不足的也就是兼容性的问题, 因此可以使用JSONP作向下的兼容

事实上前端在 CORS 上并没有多少可操作的余地, 主要的还是浏览器来处理、服务端在设置, 但是并不代表我们就不需要了解这些知识啦.

嗯, 其他的跨域方法先挖个坑..


以上所述就是小编给大家介绍的《聊聊常见的跨域问题》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Game Engine Architecture, Second Edition

Game Engine Architecture, Second Edition

Jason Gregory / A K Peters/CRC Press / 2014-8-15 / USD 69.95

A 2010 CHOICE outstanding academic title, this updated book covers the theory and practice of game engine software development. It explains practical concepts and techniques used by real game studios,......一起来看看 《Game Engine Architecture, Second Edition》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具