正确配置 CORS:跨域问题解决记录

最近和前端联调接口遇到了比较奇怪的跨域现象,连 GET 请求都被提示跨域错误。

跨域问题现象:

我们的服务都是基于 k8s 部署,在 ingress 这层都已经设置了允许跨域:

代码语言:javascript
复制
nginx.ingress.kubernetes.io/configuration-snippet: |
  # 允许来自所有来源的跨域请求。$http_origin 是一个变量,表示请求的来源地址。
  more_set_headers "Access-Control-Allow-Origin: $http_origin";
  # 允许跨域请求携带身份凭证(如 cookies)
  more_set_headers 'Access-Control-Allow-Credentials: true';
  # 设置了允许的HTTP请求方法。
  more_set_headers 'Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS';
  # 这行代码允许所有类型的HTTP头部。
  more_set_headers 'Access-Control-Allow-Headers: *';
  # 这段代码用于处理预检请求(OPTIONS请求)。如果请求方法是OPTIONS,直接返回204状态码(无内容),并结束请求。
  if ($request_method = 'OPTIONS') {
    return 204;
  }

通过排查 pod 和 ingress 的日志,发现 pod 服务并没有收到请求,则可能是 ingress 这段配置有些问题,通过上网搜索对上面的配置进行了优化。

解决:

在处理 OPTIONS 请求时又设置了一些头信息。改完之后的配置:

代码语言:javascript
复制
nginx.ingress.kubernetes.io/configuration-snippet: |
  if ($request_method = 'OPTIONS') {
    # 指定了预检请求( OPTIONS 请求)的结果可以缓存多久
    more_set_headers 'Access-Control-Max-Age: 3600';
    more_set_headers 'Access-Control-Allow-Origin: $http_origin';
    more_set_headers 'Access-Control-Allow-Credentials: true';
    more_set_headers 'Access-Control-Allow-Methods: *';
    more_set_headers 'Access-Control-Allow-Headers: *';
    return 204;
  }
  more_set_headers 'Access-Control-Allow-Origin: $http_origin';
  more_set_headers 'Access-Control-Allow-Credentials: true';
  more_set_headers 'Access-Control-Allow-Methods: *';
  more_set_headers 'Access-Control-Allow-Headers: *';

使用上面的配置就解决了跨域报错问题。通过本次问题的解决过程,又重新复习一下跨域的知识。

跨域概念

跨域(Cross-Origin Resource Sharing, CORS)是指在浏览器中,当一个网页从一个域名(origin)向另一个域名请求资源时,由于安全原因,浏览器会限制这些请求。这种限制被称为“同源策略”,它是为了防止恶意网站读取另一个网站的敏感信息。

跨域是指当一个网页试图从不同的域、协议或端口请求资源时,浏览器会阻止这些请求。例如:

代码语言:javascript
复制
从 http://example.com 向 http://api.example.com 请求资源

http://example.comhttps://example.com 请求资源

http://example.comhttp://example.com 请求资源

为了解决跨域问题,可以使用 CORS 机制。通常在 nginx 或者后端服务配置CORS规则通过设置 HTTP 头来告诉浏览器允许哪些跨域请求。常用的 CORS 头包括:

代码语言:javascript
复制
Access-Control-Allow-Origin:指定允许访问资源的域名。
Access-Control-Allow-Methods:指定允许的 HTTP 方法(例如,GET、POST)。
Access-Control-Allow-Headers:指定允许的 HTTP 头信息。
Access-Control-Allow-Credentials:指示是否允许发送 Cookie。
Access-Control-Max-Age:指定预检请求的结果可以缓存多长时间。

简单请求和复杂请求

在跨域资源共享(CORS)中,根据请求的复杂程度,浏览器将跨域请求分为简单请求和复杂请求。

简单请求是指满足以下条件的跨域请求:

请求方法:

代码语言:javascript
复制
GET
HEAD
POST

请求头信息(除了自动设置的头信息外,不能包含其他自定义头信息):

代码语言:javascript
复制
Accept
Accept-Language
Content-Language
Content-Type(仅限于以下三种内容类型)
    text/plain
    multipart/form-data
    application/x-www-form-urlencoded

对于简单请求,浏览器会自动添加一个 Origin 头信息,表示请求的来源。当服务器收到请求后,如果允许跨域访问,则在响应头中添加相应的 CORS 头信息:

代码语言:javascript
复制
Access-Control-Allow-Origin
Access-Control-Allow-Credentials(如果需要允许发送凭证,如 Cookies)

复杂请求是不满足简单请求条件的跨域请求。对于复杂请求,浏览器会在实际请求之前发送一个预检请求(preflight request)。预检请求使用 OPTIONS 方法,目的是询问服务器是否允许实际请求。

预检请求包含以下请求头信息:

代码语言:javascript
复制
Origin:请求的来源。
Access-Control-Request-Method:实际请求将使用的 HTTP 方法。
Access-Control-Request-Headers:实际请求将使用的自定义头信息。

如果服务器允许请求,则返回带有适当头信息的响应,并且浏览器会继续发送实际请求。否则,浏览器将阻止实际请求。

简单来说:

简单请求:满足特定条件(方法和头信息)的跨域请求,直接发送,不需要预检请求。

复杂请求:不满足简单请求条件的跨域请求,浏览器会先发送预检请求,以确定服务器是否允许实际请求。

总结此次跨域错误排查的经历,我们不仅复习了跨域相关的知识,还系统地探讨了跨域问题的成因及其解决方法,并详细介绍了简单请求和复杂请求的区别和处理方式。希望这些内容对你有所帮助。