解决跨域之CORS(附前后端DEMO源码)

1,091 阅读7分钟

前言

CORS对于前端开发而言,应该是一个非常重要的问题,不过对于大部分项目来说都是前后端同源,因此也就不会涉及到CORS的问题,即便是面试时大部分人也只是知道个大概,没有人去深究其中的细节实现。

直到前不久,我在开发过程中遇到了这方面的问题之后才查阅了一些资料,并详细地对CORS的机制进行了了解。因此这篇文也是给有类似经历的前端开发者写的,主要着重于实践中遇到的问题。文章后附有前后端代码可供参考。

什么是CORS

在讲CORS之前我们需要了解一下浏览器加载资源的机制:

  1. 同源策略,同源即两个 URL 的 protocol、port (en-US) (如果有指定的话)和 host 都相同的话,则这两个URL是同源。同源策略是一个重要的安全策略,它能够限制资源间的交互,帮助阻隔恶意文档,减少可能被攻击的媒介。
  2. 和同源策略相对的则是跨源,也就是我们常说的跨域,只要protocol、port和 host 有一个不同则为跨域。

下表给出了与 URL store.company.com/dir/page.ht… 的源进行对比:

URL结果原因
store.company.com/dir2/other.…同源只有路径不同
store.company.com/dir/inner/a…同源只有路径不同
store.company.com/secure.html跨源协议不同
store.company.com:81/dir/etc.htm…跨源端口不同 ( http:// 默认端口是80)
news.company.com/dir/other.h…跨源主机不同

同源策略这里不多讲了,对于大部分的项目前后端同源的话一般不会有什么问题。但当前后端分离部署产生跨域问题时,由于出于安全考虑浏览器是会限制跨域请求的,这时我们就可以采用CORS来解决问题。

CORS即Cross-Origin Resource Sharing,中文译作跨域资源共享。CORS机制允许服务器通过标识除自己以外的域来允许浏览器访问这些跨域资源,也是我们处理跨域问题中常用的一种解决方式。

(我相信大部分人在面试的时候对于跨域的解决方法都能张嘴就来,但实际操作的时候还真不一定就能非常熟练,所以还是实践出真知)

题外话,CORS在前端中算是比较重要的知识点,但是个人认为作为后端也应该对它有足够的了解,否则就会出现前端同学要求配置CORS但后端同学却不知道怎么做的尴尬情况。(别问我为什么知道)毕竟这是一个需要前后端同时配合的工作。

简单请求与预检请求

在CORS规范中,讲请求分为两种,即简单请求和预检请求。

简单请求定义如下:

  • 使用方法为GETHEADPOST之一
  • 允许人为设定的头部字段(即对CORS安全的首部字段)
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (有额外限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • 请求中的任意XMLHttpRequestUpload对象均没有注册任何事件监听器;XMLHttpRequestUpload对象可以使用XMLHttpRequest.upload属性访问
  • 请求中没有使用 ReadableStream 对象
    其中Content-Type的值仅限以下三者之一:
  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded 满足以上条件的请求即为简单请求

而不满足以上条件的请求则会触发预检请求。浏览器必须首先使用OPTIONS方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。"预检请求“可以避免跨域请求对服务器产生未预期的影响。 下图为一个预检请求的示例: image.png image.png image.png 该请求为从localhost:8080发出到localhost:3000的一个GET请求,由于端口不同而产生了跨域请求。但是因为请求头header中携带了一个自定义的字段X-Custom-Header,因此触发了预检请求,预检请求通过后才进行GET请求。

携带Cookie的请求

CORS可以基于HTTP cookies和HTTP认证信息发送身份凭证。一般对于跨域请求浏览器不会发送身份凭证信息。想要发送凭证信息需要进行相应的设置:

客户端中的请求进行如下设置后方能向服务端发送带有Cookie的请求:

withCredentials: true

服务端则需要对响应头设置:

Access-Control-Allow-Credentials: true

如果服务端未进行设置,则浏览器将不会把响应内容返回。

image.png image.png

要注意的是,对于需要携带Cookie的请求,服务器不得设置Access-Control-Allow-Origin的值为*,这种情况下请求会失败,如下图: image.png

CORS设置

下面就介绍一下常用的CORS设置。

HTTP响应头字段

Access-Control-Allow-Origin

该字段指定了允许访问该资源的外域URI:

Access-Control-Allow-Origin: http://localhost:8080

上述设置允许来自http://localhost:8080 的请求访问该资源。也可以设置为通配符*表示允许所有域的请求访问资源。对于需要携带Cookie的请求服务器可以指定值为通配符。

注:Access-Control-Allow-Origin不支持设置多个origin,只能指定一个origin或*。传入多个用逗号分隔的orgin会报错:image.png

Access-Control-Expose-Headers

在跨源访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头。默认情况下只有以下七种响应头可以暴露给外部:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Content-Length
  • Expires
  • Last-Modified
  • Pragma 如果要访问其他头,则需要服务器设置本响应头。
Access-Control-Expose-Headers: X-Kuma-Revision, X-Custom-Header

上述设置将两个自定义的响应头暴露给浏览器。我们看下下图所示的例子:

image.png

image.png

服务端自定义了一个X-Expose-Headers的响应头,但没有设置Access-Control-Expose-Headers,因此即使在浏览器打印响应对象,headers中也是拿不到的。

Access-Control-Max-Age

用于指定预检请求的结果能够被缓存多久。

Access-Control-Max-Age: 3600

上述设置表示预检请求的结果将在3600秒内是有效的。

Access-Control-Allow-Credentials

用于指定当浏览器的credentials设置为true时是否允许浏览器读取response的内容。当用在对预检请求的响应中时,它指定了实际的请求是否可以使用credentials,详见上一节。

Access-Control-Allow-Methods

用于预检请求的响应。指明了实际请求所允许使用的 HTTP 方法。

Access-Control-Allow-Methods: PUT, GET, POST, DELETE

上述设置表示该请求允许使用PUT、GET、POST、DELETE方法调用。

Access-Control-Allow-Headers

用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

Access-Control-Allow-Headers: token, originalUrl

以上设置表示实际请求中允许携带名为token和originalUrl的首部字段。

HTTP请求头字段

下面列出了可用于发起跨域请求的首部字段,这些字段不需要开发者手动设置。

Origin

Origin字段表明预检请求或实际请求的源站。

注意,在所有访问控制请求(Access control request)中,Origin 首部字段总是被发送。

Access-Control-Request-Method

用于预检请求,作用是将实际请求所使用的 HTTP 方法告诉服务器。

Access-Control-Request-Headers

用于预检请求。作用是将实际请求所携带的首部字段告诉服务器。

DEMO源码

github.com/Yayoiqz/cor… 上述的CORS设置DEMO中都有,可以调试。

参考

  1. 跨源资源共享(CORS)
  2. 深入理解 CORS