JavaScript内核与高级编程之:`JavaScript`的`CORS`:其在跨域资源共享中的工作原理。

各位观众老爷,大家好! 今天咱们聊聊前端开发里一个让人头疼,但又不得不面对的问题:CORS(Cross-Origin Resource Sharing),也就是跨域资源共享。 别害怕,听名字唬人,其实理解了原理,你会发现它也不过如此。 咱们用大白话,加上一些代码示例,把CORS给它安排的明明白白。

开场白:什么是跨域?

想象一下,你住在一个小区,小区里每家都有自己的门牌号。 你的 JavaScript 代码就像是你,想要去邻居家(另一个域名)串个门,拿点东西(请求资源)。 但是,小区保安(浏览器)说了:“等等,你们不是一家人,不能随便进出! ” 这就是跨域。

具体来说,当你的网页的域名(协议、域名、端口三者之一)和它尝试请求的资源所在的域名不同时,就会发生跨域。 浏览器为了安全,默认情况下会阻止这种跨域请求。

为什么浏览器要阻止跨域?

这得感谢浏览器的同源策略。 同源策略就像一把保护伞,防止恶意网站窃取用户的敏感信息。 如果没有同源策略,恶意网站就可以通过嵌入 <img> 标签或者 <iframe> 标签,偷偷访问其他网站的数据,想想都可怕。

CORS:跨域问题的解决方案

CORS 的出现,就是为了在保证安全的前提下,允许合理的跨域请求。 它不是禁用同源策略,而是给浏览器和服务器之间建立了一套“沟通机制”,让服务器告诉浏览器:“嘿,我知道这个请求来自哪个域,我可以信任它,允许它访问我的资源。”

CORS 的工作原理:请求头和响应头

CORS 的核心在于一系列的 HTTP 请求头和响应头。 当浏览器发起跨域请求时,它会在请求头中携带一些信息,服务器收到请求后,会根据这些信息判断是否允许这次请求。 如果允许,服务器会在响应头中添加一些 CORS 相关的头信息,告诉浏览器可以安全地访问资源。

1. 简单请求 (Simple Request)

简单请求指的是满足以下所有条件的请求:

  • 请求方法是 GETHEADPOST 之一。
  • 请求头中除了浏览器自动设置的字段(例如 User-AgentConnection)之外,只包含以下字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(只限于 application/x-www-form-urlencodedmultipart/form-datatext/plain
  • 请求中的 ReadableStream 对象未被使用。

对于简单请求,浏览器会直接发起跨域请求,并在请求头中添加一个 Origin 字段,表示请求的来源域名。

示例:

fetch('https://api.example.com/data', {
  method: 'GET'
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

浏览器发送的请求头:

GET /data HTTP/1.1
Origin: https://yourdomain.com
...其他请求头...

服务器收到请求后,如果允许跨域访问,需要在响应头中包含 Access-Control-Allow-Origin 字段。

Access-Control-Allow-Origin

这个字段指定了允许访问该资源的域名。 它的值可以是:

  • *:表示允许所有域名访问(不推荐,因为不安全)。
  • 具体的域名:例如 https://yourdomain.com,表示只允许该域名访问。
  • null:在某些特殊情况下使用,例如从 file:// 协议打开的页面。

服务器响应头示例:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yourdomain.com
Content-Type: application/json
...其他响应头...

如果 Access-Control-Allow-Origin 的值与请求头中的 Origin 字段不匹配,或者服务器没有返回这个字段,浏览器就会阻止 JavaScript 代码访问响应内容,并抛出一个 CORS 错误。

2. 预检请求 (Preflight Request)

对于不满足简单请求条件的请求,浏览器会先发起一个“预检请求”(preflight request),使用 OPTIONS 方法向服务器询问是否允许跨域请求。 预检请求中会包含一些特殊的请求头,例如 Access-Control-Request-MethodAccess-Control-Request-Headers

示例:

fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value'
  },
  body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

由于请求方法是 POST,且请求头中包含了 Content-Type: application/jsonX-Custom-Header 字段,所以这是一个需要预检的请求。

浏览器发送的预检请求头:

OPTIONS /data HTTP/1.1
Origin: https://yourdomain.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, X-Custom-Header
...其他请求头...

服务器收到预检请求后,需要返回一个包含以下 CORS 相关头的响应:

  • Access-Control-Allow-Origin 与简单请求相同,指定允许访问的域名。
  • Access-Control-Allow-Methods 指定允许的 HTTP 方法,例如 POST, GET, OPTIONS
  • Access-Control-Allow-Headers 指定允许的请求头字段,例如 Content-Type, X-Custom-Header
  • Access-Control-Max-Age 指定预检请求的缓存时间,单位是秒。 在这个时间内,浏览器不会再次发起预检请求。

服务器响应头示例:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://yourdomain.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Custom-Header
Access-Control-Max-Age: 86400
...其他响应头...

如果服务器没有返回正确的 CORS 响应头,或者浏览器判断服务器不允许跨域请求,就会阻止后续的实际请求。

CORS 相关响应头详解

咱们来详细说说这些重要的响应头:

| 响应头 | 描述 overshadow_001

  • Access-Control-Expose-Headers 指定允许浏览器访问的响应头字段。 默认情况下,浏览器只允许访问一些默认的响应头(例如 Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma)。 如果你需要访问其他的响应头,需要在服务器端通过这个字段进行声明。

需要注意的坑

  • withCredentials 当你的跨域请求需要发送 Cookie 时,需要在 JavaScript 代码中设置 withCredentialstrue,并且服务器的 Access-Control-Allow-Credentials 响应头也需要设置为 true。 如果缺少任何一步,浏览器都会阻止请求。
  • CORS 错误调试: CORS 错误信息通常比较晦涩难懂,需要仔细阅读浏览器控制台的错误信息,才能找到问题的根源。 可以使用浏览器的开发者工具来查看请求头和响应头,分析 CORS 相关的字段是否正确。
  • 服务器配置: CORS 的配置主要在服务器端进行,你需要根据你的服务器环境(例如 Node.js、Java、PHP)选择相应的 CORS 中间件或配置方法。

Node.js 中 CORS 的实现

在 Node.js 中,可以使用 cors 中间件来简化 CORS 的配置。

const express = require('express');
const cors = require('cors');
const app = express();

// 允许所有域名跨域访问
// app.use(cors());

// 自定义CORS配置
const corsOptions = {
  origin: 'https://yourdomain.com', // 允许的域名
  methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', // 允许的HTTP方法
  allowedHeaders: 'Content-Type, Authorization', // 允许的请求头
  credentials: true, // 允许携带Cookie
  preflightContinue: false, // 是否继续处理预检请求
  maxAge: 86400 // 预检请求缓存时间
};

app.use(cors(corsOptions));

app.get('/data', (req, res) => {
  res.json({ message: 'Hello from API!' });
});

app.listen(3000, () => {
  console.log('Server listening on port 3000');
});

CORS 的替代方案

除了 CORS 之外,还有一些其他的跨域解决方案,例如:

  • JSONP (JSON with Padding): 一种古老的跨域技术,利用 <img><script> 等标签的 src 属性可以跨域请求资源的特性来实现。 只能用于 GET 请求,且存在安全风险,不推荐使用。
  • 代理服务器: 在同域名的服务器上设置一个代理,将跨域请求转发到目标服务器。 浏览器向同域名的代理服务器发起请求,代理服务器再向目标服务器发起请求,并将结果返回给浏览器。
  • postMessage 一种用于在不同窗口或 <iframe> 之间进行跨域通信的机制。

总结

CORS 是解决跨域问题的关键技术,理解其工作原理对于前端开发至关重要。 希望通过今天的讲解,大家对 CORS 有了更深入的理解。 记住,CORS 不是阻碍,而是为了更好地保护我们的用户,在安全的前提下,实现跨域资源的共享。

最后的温馨提示: 在实际开发中,一定要根据具体的需求和安全要求,合理配置 CORS,避免过度开放,导致安全风险。 多查阅资料,多做实验,你就能掌握 CORS 的精髓! 祝大家开发顺利!

发表回复

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