跨域——前端 er 需要知道的 CORS

作为一个前端开发工程师,每天都要对接端口,如果你对跨域知识不了解,不知道什么是 CORS,那么我敢肯定你会经常遇到一些下面图片中的报错,对于如何解决却摸不着头脑,本文就从 CORS 入手介绍一下「跨域」的前世今生,让你面对跨域问题时再也不用发愁。


image.png

本篇文章主要分为两个部分:

  • 跨域的原理
  • CORS 如何调试

为什么会有跨域问题出现?

根本原因:浏览器通过同源策略来防止恶意网站窃取数据

这里我们需要了解这些前提:

什么是同源策略

同源是指:协议、域名、端口都相同
非同源的资源之间不能相互通信

同源政策的目的

是为了保证用户信息的安全,防止恶意的网站窃取数据。

同源策略限制了什么

如果非同源,共有三种行为受到限制

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 无法获得
  • AJAX 请求不能发送

矛盾冲突:不同源之间的合理通信也会受到限制

虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响。

解决方案:CORS、代理 和其他几种解决方案

下面详细介绍CORS和代理两种方式(最通用,最贴合我们的业务场景)

方案一:CORS

现在项目研发一般都是前后端分离的模式,前后端代码在上线的时候也是分开部署的,这个时候就肯定会涉及到跨域问题,CORS 是一种简洁,简单,标准的跨域请求方式,要发出 CORS 请求,首先我们要先了解这些前置知识:

前置知识

什么是 CORS

CORS是一种W3C标准,它允许服务器通过一些自定义的头部来限制哪些源可以访问它自身的资源。

CORS还规定对于一些可能对服务器数据产生副作用的http请求,需要在正式请求被发出之前,先发送一个options的预检请求,获知服务器是否允许该跨源请求。

注意:跨域并非是浏览器限制了发起请求,也可能是跨域请求其实可以发出去,但是返回结果被浏览器拦截了

非简单请求正式发送前,浏览器会帮忙发出一个预检请求

上面说了,CORS还规定对于一些可能对服务器数据产生副作用的http请求,需要在正式请求被发出之前,先发送一个options的预检请求,获知服务器是否允许该跨源请求。

预请求 发生在下列情况中:

  • 使用GET或POST以外的方法;
  • 利用POST发送application/x-www-form-urlencoded, multipart/form-data, or text/plain之外的Content-Type;例如,post body的Content-type为application/xml
  • 发送自定义的头信息,如x-pagination-count

操作方式

其实,实现 CORS 通信的关键在于 服务器。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

那么服务端应该怎么做呢?

服务器需要在响应头部中配置一些与跨源有关的字段

Access-Control-Allow-Origin
origin 参数的值指定了允许访问该资源的外域 URI。
对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符*,表示允许来自所有域的请求

Access-Control-Expose-Headers
在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头
例如前后端协作时,分页信息要储存在header头部,需要设置 x-pagination-count

Access-Control-Allow-Headers
指明了实际请求中允许携带的首部字段

Access-Control-Allow-Credentials
指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。
当用在对preflight预检测请求的响应中时,它指定了实际的请求是否可以使用credentials。

请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。

Access-Control-Max-Age
指定了preflight请求的结果能够被缓存多久(多少秒)

Access-Control-Allow-Methods
指明了实际请求所允许使用的 HTTP 方法

Access-Control-Allow-Headers 和 Access-Control-Expose-Headers 的区别

access-control-allow-headers:实际request请求中可以带上哪些头部

access-control-expose-headers:指的是浏览器发出 the actual request 得到 response, 浏览器可以使用/读取哪些 response 中的 headers

注意: cookie 始终遵循同源策略

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为*,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

方案二:代理

这种方案可以用于解决开发环境的跨域问题,如果你的系统上线部署时是前后端同源部署,生产环境不会出现跨域问题,那就可以用这种方案来解决开发调试的问题。

这种方式是成本最小(不需要服务端的参与,前端可以自己解决)的一种解决跨域的方案。

综上,这个跨域方案可以基于以下两个条件来使用:

  • 生产环境,前后端同源部署
  • 后端还没有配置CORS,你就可以自己撸起袖子上,不用block在别人那边

原理

代理的原理是开发时客户端这边启动一个服务端来拦截客户端发出去的请求,该服务端代为发送请求至真实的服务端,这样通信就是发生在服务端和服务端之间,也就不会出现所谓的跨域问题。

操作方式

webpack 文档

其他方案

在 CORS 规范出来之前,还有一些其他的跨域解决方案(不推荐使用)

  • document.domain + iframe
  • 动态创建script
  • location.hash + iframe
  • window.name + iframe
  • postMessage
  • JSONP
  • web sockets

其实这些解决方案在CORS这个标准出现之后就比较少有人用了,我也没有真正使用过这几个方案,这里不过多讨论,感兴趣的可以参考 这篇文章 看下

CORS 的调试

分析headers

大部分的 CORS 错误都可以归因与请求头和响应头的不匹配。

所以,CORS 出现错误,分析的第一个动作就是分析 headers,大部分的 CORS 错误都可以归因与请求头和响应头的不匹配,例如:

image

请求头中定义了 Access-Control-Request-Method: POST 以及 Access-Control-Request-Headers: authorization,content-type, 但是服务端没有与之匹配的响应头,所以报错。

辅助工具

浏览器的开发者工具

需要关注 console 和 network 两个面板,这个是我们最常用的方法,以后可以收集一些常见报错放上来,现在不多赘述。

抓包工具

简单请求(没有预检请求的那种)浏览器是不会给你展示具体的请求信息(响应头和请求头),这个时候你要怎么查看头部信息来定位问题呢?可以使用网络抓包工具,常用的有 Wireshark或Fiddler。

(使用教程网上很多,可以自行搜索查看)

curl
要用curl来模拟跨域请求(其实关键是模拟options请求),我们需要先来观察一下options请求的特征:

  • 具有OPTIONS的HTTP方法
  • Origin标头
  • Access-Control-Request-Method标头。

所以要使用curl模拟预检请求,我们需要模仿这三个特征。
(同样的道理也适用于postman,反正关键是你要设定正确的请求头才能模拟出 options请求)

curl --verbose -H "Origin: http://localhost:9090" -H "Access-Control-Request-Method: POST" -X OPTIONS http://xxx.xxx.xxx:8080/a/b

下图是curl的全部内容:


image

反正这里的请求结果里,如果响应的状态码不是200,那你就大胆地去和后端 battle 吧(当然前提是你已经正确设置了请求头,模拟出来的确实是options请求)

总结

无论使用什么辅助工具,其实总结起来就是三步:

  1. 关注请求和响应头
  2. 比较headers以查看是否存在不匹配的
  3. 解决问题
    1. 更新客户端的 request headers 以发送正确的请求
    2. 更新服务器的 response headers 以允许客户端来请求
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容