CORS(跨域资源共享)原理与跨域请求处理

CORS:跨域请求的爱恨情仇,以及如何优雅地化解它们

各位观众老爷们,晚上好!欢迎来到今晚的“跨域请求大讲堂”。我是你们的老朋友,人称“代码界段子手”的程序猿老王。今天咱们不聊诗词歌赋,也不谈人生理想,就来聊聊这个让前端工程师们又爱又恨,仿佛隔壁老王一般存在的 —— CORS (Cross-Origin Resource Sharing)!

我相信,在座的各位前端大佬,或多或少都曾被 CORS 这个磨人的小妖精折磨过。明明代码逻辑没毛病,浏览器却冷冰冰地抛出一个 CORS 错误,让你对着屏幕抓耳挠腮,百思不得其解。别慌!今天老王就带你拨开迷雾,看清 CORS 的本质,掌握跨域请求的正确姿势,让你的代码不再为“出身”所困!

一、跨域:一场注定要发生的“门不当户不对”的爱情故事

要理解 CORS,首先要搞清楚“域”的概念。 简单来说,域就是指网站的“出身”,它由协议(protocol)、域名(domain)和端口号(port)三部分组成。 比如:

  • http://www.example.com:8080 就是一个域。

如果两个网页的协议、域名和端口号中任意一个不同,那么它们就属于不同的域。

想象一下,你是一个身价千万的富家千金 http://www.my-website.com,而隔壁住着一个穷小子 http://api.some-other-domain.com,你对他一见倾心,想和他私定终身(发起请求)。但是,你的父母(浏览器)却跳出来阻止:“不行!门不当户不对!你俩不能在一起!”

这就是跨域的本质:浏览器为了安全起见,默认禁止 JavaScript 代码从一个域发起请求到另一个域,除非对方明确表示允许。 这就像一道无形的防火墙,保护着用户的隐私和数据安全。

为什么要这么做呢?

想想看,如果没有同源策略,恶意网站就可以通过 JavaScript 代码偷偷地获取你的银行账户信息、登录凭证等等,然后发送到自己的服务器上。 细思极恐啊! ?

二、CORS:跨域的通行证,打开爱情之门的钥匙?

CORS,全称跨域资源共享,它就像一张通行证,允许服务器声明哪些源(域)可以通过浏览器访问其资源。它是一种基于 HTTP 头的机制,让服务器告诉浏览器,哪些源是被允许的。

想象一下,你的父母(浏览器)知道你对隔壁穷小子是真爱,于是给你们开了一个小小的“后门”:只要穷小子的父母(服务器)同意,你们就可以光明正大地在一起啦! 这就是 CORS 的作用。

CORS 的工作原理

当浏览器发起跨域请求时,它会在请求头中添加一个 Origin 字段,这个字段告诉服务器,请求来自哪个源。

服务器收到请求后,会根据 Origin 字段和自身的配置,决定是否允许这个请求。 如果允许,服务器会在响应头中添加一些特殊的 CORS 相关的字段,告诉浏览器可以放行。

浏览器收到响应后,会检查响应头中的 CORS 字段,如果符合要求,就允许 JavaScript 代码访问响应内容;否则,浏览器会阻止 JavaScript 代码访问响应内容,并抛出一个 CORS 错误。

CORS 请求的类型

CORS 请求分为两种:简单请求(Simple requests)和预检请求(Preflighted requests)。

  • 简单请求:满足以下所有条件的请求被认为是简单请求:

    • 请求方法是 GETHEADPOST
    • 请求头中只包含以下字段:
      • Accept
      • Accept-Language
      • Content-Language
      • Content-Type (值为 application/x-www-form-urlencodedmultipart/form-datatext/plain)
      • DPR
      • Downlink
      • ECT
      • RRT
      • Save-Data
      • Viewport-Width
      • Width
    • 请求中没有使用 ReadableStream 对象。

    简单请求的处理流程是:浏览器直接发送请求,并在请求头中添加 Origin 字段。服务器收到请求后,如果允许跨域访问,就在响应头中添加 Access-Control-Allow-Origin 字段。

  • 预检请求:不满足简单请求条件的请求,都被认为是预检请求。

    预检请求的处理流程是:

    1. 浏览器先发送一个 OPTIONS 请求,这个请求被称为预检请求,用于询问服务器是否允许真正的请求。
    2. 服务器收到 OPTIONS 请求后,会检查请求头中的信息,如果允许跨域访问,就在响应头中添加一些 CORS 相关的字段,例如 Access-Control-Allow-MethodsAccess-Control-Allow-Headers 等。
    3. 浏览器收到 OPTIONS 请求的响应后,会检查响应头中的 CORS 字段,如果符合要求,就发送真正的请求;否则,浏览器会阻止请求,并抛出一个 CORS 错误。

三、CORS 相关的 HTTP 头部字段:爱情宣言的密码?

CORS 的核心在于服务器通过 HTTP 头部来声明允许哪些源访问其资源。 让我们来了解一下这些关键的头部字段:

头部字段 作用 示例
Access-Control-Allow-Origin 指定允许访问该资源的源。 可以设置为一个具体的源(例如 http://www.example.com),也可以设置为 *,表示允许所有源访问。 Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods 指定允许的 HTTP 方法。 用于预检请求的响应头中,告诉浏览器允许使用哪些 HTTP 方法发起请求。 Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers 指定允许的请求头。 用于预检请求的响应头中,告诉浏览器允许在请求头中携带哪些字段。 Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header
Access-Control-Allow-Credentials 指定是否允许携带凭证(例如 Cookie、HTTP 认证信息)。 如果设置为 true,表示允许携带凭证;否则,表示不允许携带凭证。 注意:如果请求中需要携带凭证,则 Access-Control-Allow-Origin 不能设置为 *,必须设置为一个具体的源。 Access-Control-Allow-Credentials: true
Access-Control-Max-Age 指定预检请求的结果可以被缓存的时间。 用于预检请求的响应头中,告诉浏览器可以将预检请求的结果缓存多长时间,避免每次都发送预检请求。 Access-Control-Max-Age: 3600 (单位:秒)
Access-Control-Expose-Headers 指定允许浏览器访问的响应头。 默认情况下,浏览器只能访问一些标准的响应头(例如 Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma)。 如果服务器想要允许浏览器访问其他的响应头,就需要通过这个字段来指定。 Access-Control-Expose-Headers: X-Custom-Header, X-Request-Id
Origin 客户端发送的请求头,表明请求的来源(源)。 Origin: http://www.my-website.com
Vary 服务器用来告诉浏览器,响应依赖于哪些请求头。 当浏览器收到一个响应后,会根据请求头和 Vary 头的值,决定是否使用缓存。 如果 Vary 头的值包含 Origin,则浏览器会根据 Origin 头的不同,缓存不同的响应。 这可以避免在多个源访问同一个资源时,出现缓存问题。 Vary: Origin

四、解决 CORS 问题的几种常见姿势:解锁爱的各种方式 ?

掌握了 CORS 的原理,接下来就是解决 CORS 问题的实战环节了。 老王为大家准备了几种常见的解决方案,总有一款适合你:

  1. 服务器端设置 CORS 头部

    这是最常见,也是最推荐的解决方案。 通过在服务器端设置 CORS 相关的 HTTP 头部,告诉浏览器允许哪些源访问该资源。

    不同的编程语言和框架,设置 CORS 头部的方式略有不同。 这里以 Node.js + Express 为例:

    const express = require('express');
    const app = express();
    
    app.use((req, res, next) => {
      res.header('Access-Control-Allow-Origin', '*'); // 允许所有源访问,生产环境不推荐
      res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
      res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      next();
    });
    
    app.get('/api/data', (req, res) => {
      res.json({ message: 'Hello from the API!' });
    });
    
    app.listen(3000, () => {
      console.log('Server listening on port 3000');
    });

    注意: 在生产环境中,Access-Control-Allow-Origin 不建议设置为 *, 最好设置为具体的源,以提高安全性。

  2. JSONP (JSON with Padding)

    JSONP 是一种古老的跨域解决方案,它利用了 <script> 标签可以跨域加载资源的特性。

    JSONP 的原理是:客户端创建一个 <script> 标签,将请求 URL 设置为服务器提供的 JSONP 接口,并在 URL 中指定一个回调函数名。 服务器收到请求后,将数据包裹在回调函数中,返回给客户端。 客户端执行回调函数,即可获取数据。

    JSONP 只能用于 GET 请求,并且存在一些安全风险,因此不推荐使用。 现在已经很少使用了,属于时代的眼泪 ?。

  3. 代理服务器

    代理服务器是指位于客户端和服务器之间的服务器。 客户端先将请求发送到代理服务器,然后代理服务器再将请求转发到目标服务器。 目标服务器返回响应后,代理服务器再将响应转发给客户端。

    由于代理服务器和目标服务器属于同一个域,因此不存在跨域问题。 客户端只需要和代理服务器通信,就可以间接地访问目标服务器的资源。

    这种方式的优点是可以解决所有类型的跨域问题,缺点是需要部署和维护代理服务器,增加了一定的复杂性。

  4. Nginx 反向代理

    与代理服务器类似,Nginx 也可以作为反向代理服务器,将客户端的请求转发到目标服务器。

    Nginx 反向代理的优点是性能高、稳定性好,并且可以配置各种缓存策略,提高网站的访问速度。

    配置 Nginx 反向代理也很简单,只需要在 Nginx 的配置文件中添加以下内容:

    server {
      listen 80;
      server_name www.my-website.com;
    
      location /api/ {
        proxy_pass http://api.some-other-domain.com/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      }
    }
  5. WebSocket

    WebSocket 是一种全双工通信协议,可以在客户端和服务器之间建立持久的连接。

    WebSocket 不受同源策略的限制,可以跨域通信。

    WebSocket 的优点是实时性好、效率高,适合用于需要实时通信的场景,例如聊天室、在线游戏等。

  6. CORS 插件

    有些浏览器提供了 CORS 插件,可以自动为跨域请求添加 CORS 相关的头部。

    这种方式的优点是简单易用,不需要修改代码。 缺点是依赖于浏览器插件,用户需要安装插件才能生效。

五、CORS 问题的排查与调试:做个合格的“侦探” ?️‍♂️

当遇到 CORS 错误时,不要慌张,要像一名优秀的侦探一样,一步步地排查问题。

  1. 查看浏览器控制台

    浏览器控制台会显示 CORS 错误的详细信息,包括错误原因、请求头和响应头。 仔细阅读错误信息,可以帮助你快速找到问题所在。

  2. 检查请求头和响应头

    使用浏览器的开发者工具,检查请求头和响应头,确认 OriginAccess-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers 等字段是否正确。

  3. 使用 CORS 调试工具

    有一些在线的 CORS 调试工具,可以帮助你模拟跨域请求,并分析请求头和响应头,找出问题所在。

  4. 检查服务器配置

    确认服务器是否正确配置了 CORS 相关的头部。 不同的编程语言和框架,配置 CORS 头部的方式略有不同,需要仔细阅读官方文档。

  5. 尝试不同的解决方案

    如果一种解决方案不起作用,可以尝试其他的解决方案。 不同的场景可能需要使用不同的解决方案。

六、总结:跨域,不再是拦路虎,而是垫脚石!?

CORS 并不是一个难以理解的概念,只要掌握了它的原理,就可以轻松地解决跨域问题。 希望通过今天的讲解,大家能够对 CORS 有更深入的了解,不再害怕遇到 CORS 错误。

记住,跨域就像一场恋爱,需要双方的配合和理解。 只要客户端(浏览器)和服务器端(API)都遵循 CORS 规则,就可以建立起安全可靠的跨域连接。

最后,祝愿大家的代码都能顺利地跨越域的限制,自由地翱翔在互联网的海洋中! 感谢大家的收看,咱们下期再见! ?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注