返回博客

前端 CORS 跨域问题详解

本文详细分析了前端 CORS 跨域问题,特别是涉及 cookie 的场景,并提供了多种解决方案,包括在服务端设置 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials,以及在客户端设置 withCredentials 等。

Mt.r
|

问题

场景

页面:http://localhost:1024

接口:http://localhost:3001

浏览器:Chrome 92+

错误信息

Access to XMLHttpRequest at 'http://localhost:3001/event-tracking' from origin 'http://localhost:1024' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

讲的是 Access-Control-Allow-Origin 的值不能是 *,而是具体的域名

问题排查及分析

通常情况下,我们会在接口服务端设置 Access-Control-Allow-Origin 的值为 *,这样就可以解决跨域问题了。

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Credentials', 'true');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
  res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  if (req.method === 'OPTIONS') {
    res.send(200);
  } else {
    next();
  }
});

但是还是有一些特殊的场景,就是请求的时候带上 cookie,这个时候就会出现上面的问题。

官方说明及建议

见文档 HTTP access control (CORS)

备注:当响应的是附带身份凭证的请求时,服务端 必须 明确 Access-Control-Allow-Origin 的值,而不能使用通配符“*”。

在响应附带身份凭证的请求时:

  • 服务器不能将 Access-Control-Allow-Origin 的值设为通配符 *,而应将其设置为特定的域,如:Access-Control-Allow-Origin: https://example.com。 服务器不能将 Access-Control-Allow-Headers 的值设为通配符 *,而应将其设置为首部名称的列表,如:Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
  • 服务器不能将 Access-Control-Allow-Methods 的值设为通配符 *,而应将其设置为特定请求方法名称的列表,如:Access-Control-Allow-Methods: POST, GET
  • 对于附带身份凭证的请求(通常是 Cookie),服务器不得设置 Access-Control-Allow-Origin 的值为 *

这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为 *,请求将会失败。而将 Access-Control-Allow-Origin 的值设置为 https://example.com,则请求将成功执行。

另外,响应首部中也携带了 Set-Cookie 字段,尝试对 Cookie 进行修改。如果操作失败,将会抛出异常。

问题解决方案

在需要跨域携带 cookie 时,要把 withCredentials 设置为 true,比如:

const xhr = new XMLHttpRequest();
xhr.withCredentials = true;

Express 中设置 Access-Control-Allow-Origin 的值为 *,并且设置 Access-Control-Allow-Credentials 的值为 true,比如:

res.header("Access-Control-Allow-Origin", "http://localhost:4200");
res.header('Access-Control-Allow-Credentials', true);

如果用的是 cors 包,可以这样设置:

app.use(cors({
  origin: 'http://localhost:1024',
  credentials: true
}));

还有一些高级写法

const cors = require('cors');
const whitelist = ['http://localhost:4200', 'http://example2.com'];
const corsOptions = {
  credentials: true, // This is important.
  origin: (origin, callback) => {
    if(whitelist.includes(origin))
      return callback(null, true)

      callback(new Error('Not allowed by CORS'));
  }
}

app.use(cors(corsOptions));

参考链接