【学习记录】浏览器的跨域资源共享(CORS)

169 阅读3分钟

跨域资源共享

跨域资源共享(CORS)是一种浏览器机制,允许网页从不同源(协议、域名或端口)的服务器请求资源。它是为了解决浏览器的同源策略(Same-Origin Policy)限制而设计的。

1. 什么是跨域?

跨域是指浏览器从一个源(Origin)向另一个源发起请求。同源策略要求协议、域名和端口完全相同,否则就是跨域。

  • 同源示例
    • https://example.comhttps://example.com/api 是同源。
  • 跨域示例
    • https://example.comhttps://api.example.com 是跨域(域名不同)。
    • http://example.comhttps://example.com 是跨域(协议不同)。
    • https://example.comhttps://example.com:8080 是跨域(端口不同)。

2. CORS 的工作原理

CORS 通过在 HTTP 请求和响应中添加特定的头部字段来实现跨域资源共享。

2.1 简单请求(Simple Request)

简单请求满足以下条件:

  • 请求方法是 GETPOSTHEAD
  • 请求头只包含以下字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(值为 application/x-www-form-urlencodedmultipart/form-datatext/plain)。

流程

  1. 浏览器直接发送跨域请求。
  2. 服务器在响应头中添加 Access-Control-Allow-Origin,指定允许访问的源。
  3. 浏览器检查响应头,如果允许访问,则返回数据;否则,抛出错误。

示例

  • 请求:

    GET /data HTTP/1.1
    Host: api.example.com
    Origin: https://example.com
    
  • 响应:

    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: https://example.com
    Content-Type: application/json
    
    {"data": "Hello, CORS!"}
    

2.2 预检请求(Preflight Request)

如果请求不满足简单请求的条件(如使用 PUT 方法或自定义请求头),浏览器会先发送一个预检请求(OPTIONS 方法)来确认服务器是否允许跨域请求。

流程

  1. 浏览器发送预检请求(OPTIONS)。
  2. 服务器响应预检请求,指定允许的请求方法、请求头和源。
  3. 如果预检请求通过,浏览器发送实际请求。
  4. 服务器响应实际请求。

示例

  • 预检请求:

    OPTIONS /data HTTP/1.1
    Host: api.example.com
    Origin: https://example.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: Content-Type
    
  • 预检响应:

    HTTP/1.1 204 No Content
    Access-Control-Allow-Origin: https://example.com
    Access-Control-Allow-Methods: PUT
    Access-Control-Allow-Headers: Content-Type
    
  • 实际请求:

    PUT /data HTTP/1.1
    Host: api.example.com
    Origin: https://example.com
    Content-Type: application/json
    
    {"data": "Hello, CORS!"}
    
  • 实际响应:

    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: https://example.com
    Content-Type: application/json
    
    {"data": "Request successful!"}
    

3. CORS 相关 HTTP 头部字段

3.1 请求头

  • Origin:表示请求的源(协议 + 域名 + 端口)。
  • Access-Control-Request-Method:用于预检请求,表示实际请求的方法(如 PUT)。
  • Access-Control-Request-Headers:用于预检请求,表示实际请求的自定义头(如 Content-Type)。

3.2 响应头

  • Access-Control-Allow-Origin:指定允许访问的源(如 https://example.com*)。
  • Access-Control-Allow-Methods:指定允许的请求方法(如 GET, POST, PUT)。
  • Access-Control-Allow-Headers:指定允许的请求头(如 Content-Type)。
  • Access-Control-Allow-Credentials:指定是否允许发送凭据(如 Cookie)。
  • Access-Control-Max-Age:指定预检请求的缓存时间(秒)。

4. CORS 的常见问题

4.1 跨域请求被阻止

  • 原因:服务器未正确配置 Access-Control-Allow-Origin
  • 解决方法:确保服务器响应头中包含 Access-Control-Allow-Origin,并设置为允许的源或 *

4.2 预检请求失败

  • 原因:服务器未正确处理预检请求(OPTIONS)。
  • 解决方法:确保服务器支持 OPTIONS 方法,并正确返回 Access-Control-Allow-MethodsAccess-Control-Allow-Headers

4.3 跨域请求无法携带凭据

  • 原因:默认情况下,跨域请求不会发送凭据(如 Cookie)。

  • 解决方法

    • 在请求中设置 credentials: 'include'(Fetch API)或 withCredentials: true(XMLHttpRequest)。
    • 服务器响应头中设置 Access-Control-Allow-Credentials: true

5. CORS 的示例代码

5.1 服务器端(Node.js + Express)

const express = require("express");
const app = express();

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "https://example.com");
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT");
  res.header("Access-Control-Allow-Headers", "Content-Type");
  res.header("Access-Control-Allow-Credentials", "true");
  next();
});

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

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

5.2 客户端(Fetch API)

fetch("https://api.example.com/data", {
  method: "GET",
  credentials: "include", // 允许发送凭据
})
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error("Error:", error));

6. 总结

  • CORS 是一种浏览器机制,用于解决跨域资源共享问题。
  • 简单请求直接发送,复杂请求需要先发送预检请求。
  • 服务器通过设置响应头(如 Access-Control-Allow-Origin)来控制跨域访问。
  • 合理配置 CORS 可以提升网站的安全性和兼容性。