那些年我们“跨”过的“域”(接口篇)

作为前端开发,从入行起,应该就接触过跨域的概念。工作中,在与服务端配合时,也经常需要处理跨域相关问题。如果你不能理解到底什么是跨域,那在与服务端配合解决跨域问题时,你可能就要落入对方的掌控之中了(哈哈,开个玩笑)。

为了避免这一尴尬的境地,今天我来带着大家一起重温并巩固一下以下内容:什么是跨域,跨域有哪些实际的开发场景,有哪些方式可以快速的处理跨域问题。

整篇内容没有涵盖网上所有的与跨域“沾边”的知识,而是从实际场景出发,旨在解决工作中经常遇到的跨域问题。

一、什么是跨域?

1.1 同源策略 VS 跨域

如果让你直接定义什么是跨域,你可能会发现很难定义。所以需要借助与之对立的概念——同源策略(SOP,Same-Origin Policy)。只要不是同源的,那就是跨域的。

同源策略是浏览器中一个极为至关重要的安全机制,可以用来限制某一源内的文档或脚本与另一源内的资源如何进行交互。其目的在于隔离潜在的恶意文档,减少可能存在的攻击。

而对于同源的定义是:协议、主机名(host)以及端口三者均相同

维基百科中对于URI的结构组成说明如下:

[协议名]://[用户名]:[密码]@[主机名]:[端口]/[路径]?[查询参数]#[片段ID]

常规情况下,我们无需在URI中带有用户名密码等信息用于验证。这只是一个完整的URI组成示例。

所以对于同源,只要URI中协议名、主机名、端口三者有其中一条不同,则视为不同源。不同源之间请求资源,则为跨域。其中主机名部分,主域和子域视为不同、域名与其对应的IP也视为不同,这就是说看着必须得一样。

1.2 跨域的限制

当存在跨域问题时,浏览器会做出一定的限制措施。主要包括以下三点:

  1. Cookie、LocalStorage 和 IndexDB 无法读取。
  2. DOM 无法获得。
  3. AJAX 请求被拦截。

注:Cookie获取不检测端口

二、常见的跨域开发场景/业务场景

撇开场景谈概念,一定是晦涩难懂的,开始说了,本文旨在解决实际工作中遇到的跨域问题。下面我们来一起看看工作过程中比较常见的跨域场景。

2.1 前后端分离:纯前端 + 接口层 (开发模式)

在前后端分离的开发模式下,开发环境应该用webpack的居多(当然有的可能不是,以此为例),与之相应的web服务器就是webpack-dev-server。这类开发模式的架构一般如下:

这一架构下,dev-server中的页面如果通过ajax直接调用服务端的API会存在跨域问题。

2.2 前后端分离:纯前端 + 接口层 (生产模式)

与第一种方式相似,前后端分离的项目在开发完成后,往往通过nginx等作为静态资源服务器,前端页面直接通过ajax发送请求,依然存在跨域请求问题。

架构如下:

2.3 前后端分离:纯前端 + BFF + 接口层

有时候,我们所要调用的接口层可能并不只是给我们提供服务,他们只会提供一些通用的数据,我们需要对数据进行一定程度的二次加工;也可能我们需要自己给前端页面提供一些通用的功能,如图片上传等。这时,就需要在前端页面和接口层之间增加一个BFF层(Backends For Frontends)。

BFF层一般由前端维护,所以使用Node.js居多。

这一架构如下:

使用这种架构其实本身已经解决了跨域问题,是一种跨域解决方式,后面我们再细说。

2.4 服务端渲染 + web服务器(不跨域)

最后一种是最原始的web服务架构,html页面以及其他静态资源都直接从服务器获取,接口也直接由所在服务器处理。这种方式不存在跨域问题。前端和服务端逻辑完全绑定,互相支撑提供服务。

三、跨域的解决方式

前面我们提到,跨域是浏览器的限制。所以我们想解决跨域问题可以有两个方向,第一是绕开浏览器限制,第二是通过浏览器支持的方式来允许跨域。

下面我们分别会介绍三种绕开浏览器限制的解决方式,分别为webpack-dev-server代理/Nginx代理转发/服务器代理,以及浏览器本身支持的CORS方式。

没有大家耳熟能详的JSONP,大家自行科普一下吧。

3.1 webpack-dev-server代理

对于上面说到的“前后端分离:纯前端 + 接口层 (开发模式)”这一场景,当我们在http://co.com的页面上直接调用http://api.co.com的接口时,会出现跨域问题。

我们可以将所有的接口请求都从http://co.com发出,如http://co.com/api/getSomeData(额外加了/api,方便统一转发),最后通过proxy配置代理,转发到最终的接口服务器http://api.co.com/getSomeData

proxy配置如下:

devServer: {
  proxy: {
    '/api': {
      target: 'http://api.co.com',
      // 如果转发后的pathname需要改变,可以通过以下方式重写
      // 下面是把api前缀去掉
      pathRewrite: {
        '^/api/': '',
      },
    },
  }
}

通过上述方式,我们可以在接口请求发起的时候,统一从当前所在源发起,最后通过proxy代理的方式转发到真正的接口层。这样就绕开了调用接口时浏览器的同源限制。

3.2 Nginx代理转发

针对第二部分提到的“前后端分离:纯前端 + 接口层(生产模式)”这一场景,这时我们没有webpack-dev-server可用了,不过没关系,我们在使用nginx作为静态资源服务器时,也可以做一些代理转发。可以将接口请求全部转发到对应接口服务器。

配置如下:

location /api {
  proxy_redirect off;        
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-NginX-Proxy true;
  # 转发时重写地址
  rewrite ^/api/(.*)$ /$1 break;
  # 转发目的地
  proxy_pass http://api.co.com;
}

这种方式其实与第一种类似,只不过是通过不同的方式进行代理。这种方式也是通过绕过浏览器限制的方式解决跨域的。

3.3 服务器转发

第二部分的第三种架构“前后端分离:纯前端 + BFF + 接口层”,这种架构其实就已经解决了跨域的问题,前端页面的所有接口都由BFF层进行管理。

对于BFF层,可以通过添加中间件或者其他的方式对于接口进行拦截。如果是静态资源或者是当前服务所提供的接口,则直接处理。如果是调用api的接口请求,将其转发到对应的服务即可。

不同的框架有不同的方式来处理接口拦截与转发,所以此处没有代码。

3.4 CORS 跨域资源共享

跨域资源共享(CORS) 是一种机制,服务端可以通过额外的 HTTP 头来告诉浏览器允许某一源内的Web应用访问不同源服务器上的指定资源。

CORS使用通用的跨域解决方式,需要服务端配合进行实现。

这里面会涉及到简单请求以及预检请求的概念。关于什么是简单请求,大家可以移步MDN看下详细的定义,这里不再详述了。

这两种请求的区别在于,对于预检请求,浏览器必须首先使用OPTIONS方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的HTTP请求。

  1. 为什么要区分简单请求和预检请求可以参考贺老的这篇文章
  2. IE8/9不支持CORS,通过XDomainRequest来实现

3.4.1 简单请求

对于简单请求,服务端通过简单的设置Access-Control-Allow-Origin: *即可允许任意来源进行跨域请求。如果只想允许来自http://co.com的访问,可以设置Access-Control-Allow-Origin: http://co.com

通信过程示意图如下:

注:在发起跨域请求时,浏览器会在请求头字段中自动带上Origin字段,值为当前所在域。

3.4.2 预检请求

对于预检请求,服务端需要额外再多做一些事情。如下步骤:

  1. 首先,发起预检请求,带上真实请求的Method。
  2. 服务端判断是否允许跨域请求,如果允许则返回允许的来源、允许的请求Methods以及预检请求的有效时长(有效时间内,同一请求无需再次发送预检请求,不过不可以任意设置,浏览器有最大时长限制)。
  3. 客户端发起真实的跨域请求。
  4. 服务端返回。

通信过程示意图如下:

需要注意的是,服务端在处理预检请求时,如果允许跨域,服务端只需要设置对应的响应头,然后直接返回即可,无需其他处理。

3.4.3 附带身份凭证的请求

常规来说,我们的请求都需要带有身份凭证(如Cookie),这时服务器端的响应中需要额外设置Access-Control-Allow-Credentials: true,如果未设置,浏览器将不会把响应内容返回给请求的发送者。

还有个别不是很常用的请求头和响应头字段,大家可前往MDN查看完整的列表。

总结

如上,我们从以下三个方面介绍了跨域:什么是跨域,跨域有哪些实际的开发场景,有哪些方式可以快速的处理跨域问题。

大家在遇到跨域问题时,可以根据具体的场景选择绕过跨域问题,还是选择通用的CORS模式来解决。

最后希望大家看完这篇文章之后,都会是『那些年我们“跨”过的“域”(接口篇)』。而不是『那些年我们都没“跨”过去的“域”』🤣 🤣 🤣。

参考链接

  1. https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
  2. https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
  3. https://zhuanlan.zhihu.com/p/55858103
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,841评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,415评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,904评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,051评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,055评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,255评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,729评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,377评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,517评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,420评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,467评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,144评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,735评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,812评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,029评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,528评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,126评论 2 341

推荐阅读更多精彩内容

  • 题目1.什么是同源策略? 同源策略(Same origin Policy): 浏览器出于安全方面的考虑,只允许与本...
    FLYSASA阅读 1,704评论 0 6
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    他方l阅读 1,056评论 0 2
  • 1. 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScri...
    cbw100阅读 6,295评论 2 86
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    Yaoxue9阅读 1,280评论 0 6
  • 浏览器在请求不同域的资源时,会因为同源策略的影响请求不成功,这就是通常被提到的“跨域问题”。作为前端开发,解决跨域...
    SCQ000阅读 2,535评论 1 52