Nginx Ingress跨域配置说明

CORS(Cross-Origin Resource Sharing)是一种网络安全协议,用于允许或限制一个域(例如example.com)的网页访问另一个域(例如api.example.com)的资源。本文介绍典型的CORS配置场景,并结合示例说明如何实现跨域资源的共享。

使用场景

在Web开发实践中,跨域请求是常见的问题,尤其是在如下场景中:

场景

描述

示例

前后端分离

前端(客户端)应用和后端(API)服务部署在不同的域或端口上。前端向后端发送XHR(XMLHttpRequest)或Fetch请求时,需要配置CORS。

一个前端应用程序(例如项目管理工具)部署在https://frontend.example.com,它通过API与后端服务通信,后端服务部署在https://api.example.com。

单页面应用(SPA)

单页面应用程序(例如React、Vue、Angular)通过API与服务器进行通信。如果API和前端应用不在同一个域,需要配置CORS。

单页面应用,部署在https://dailydose.example.com。在首次访问时,页面中JavaScript、CSS和其他资源被加载,实现了整个应用界面。

第三方服务集成

当需要从客户端直接请求第三方服务的API时,如果第三方服务未配置CORS,则需要通过本地服务器配置CORS来进行代理。

https://dailydose.example.com的网页中集成了https://weatherapi.example.com,用于提供实时天气预报的API。

配置CORS

CORS机制是通过设置HTTP响应头来实现的,可以在如Nginx、Apache等Web服务器上设置,或者在后端应用代码中定义。主要涉及以下HTTP头:

名称

描述

示例

Access-Control-Allow-Origin

指定哪些域可以访问资源。

Access-Control-Allow-Origin: https://example.com

Access-Control-Allow-Methods

指定允许的方法类型,例如GET、POST、PUT等。

Access-Control-Allow-Methods: GET, POST, PUT

Access-Control-Allow-Headers

指定允许的自定义请求头字段。

Access-Control-Allow-Headers: Content-Type, Authorization

Access-Control-Allow-Credentials

指示是否可以发送带有凭证的请求(如Cookies、HTTP认证信息)。

Access-Control-Allow-Credentials: true

Access-Control-Expose-Headers

指示哪些响应头可以在浏览器中访问。

Access-Control-Expose-Headers: X-Custom-Header

Access-Control-Max-Age

指定浏览器可以缓存预检请求结果的最大时间(以秒为单位)。

Access-Control-Max-Age: 86400 (24小时)。

通过Nginx Ingress Controller配置CORS

在Kubernetes环境中,当使用Nginx Ingress控制器配置CORS策略时,我们可以通过在Ingress资源的Annotations部分添加指定注解来实现。下面的表格展示了用于CORS设置的Nginx Ingress常用注解。更多信息,请参见Enable CORS

注释

描述

示例

nginx.ingress.kubernetes.io/enable-cors

启用CORS设置。

nginx.ingress.kubernetes.io/enable-cors: "true"

nginx.ingress.kubernetes.io/cors-allow-origin

指定Access-Control-Allow-Origin的值。

nginx.ingress.kubernetes.io/cors-allow-origin: "https://example.com"

nginx.ingress.kubernetes.io/cors-allow-methods

指定Access-Control-Allow-Methods的值。

nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS"

nginx.ingress.kubernetes.io/cors-allow-headers

指定Access-Control-Allow-Headers的值。

nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range"

nginx.ingress.kubernetes.io/cors-allow-credentials

指定Access-Control-Allow-Credentials的值。

nginx.ingress.kubernetes.io/cors-allow-credentials: "true"

nginx.ingress.kubernetes.io/cors-expose-headers

指定Access-Control-Expose-Headers的值。

说明

Nginx Ingress Controller版本为v0.44及以上。

nginx.ingress.kubernetes.io/cors-expose-headers: "Content-Length,Content-Range"

nginx.ingress.kubernetes.io/cors-max-age

指定Access-Control-Max-Age的值。

nginx.ingress.kubernetes.io/cors-max-age: "86400"

示例:前后端分离场景

一个前端应用程序(例如项目管理工具)部署在https://frontend.example.com,通过API与后端服务通信,而后端服务部署在https://api.example.com。

以下示例Ingress YAML配置了CORS相关的Annotation。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/enable-cors: "true"     # 启用CORS。
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"  # 允许所有域访问。
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS"  # 允许的HTTP方法。
     # 允许的自定义请求头。
    nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range" 
    nginx.ingress.kubernetes.io/cors-expose-headers: "Content-Length,Content-Range"  # 暴露的响应头。
    nginx.ingress.kubernetes.io/cors-max-age: "86400"  # 预检请求缓存时间。
spec:
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: example-service
            port:
              number: 80

验证跨域配置是否生效

curl -X OPTIONS -H 'Origin: <来源站点>' <您配置了跨域规则所在Ingress对应的url>curl -X OPTIONS -H 'Origin: https://frontend.example.com'  http://api.example.com/test跨域规则会在原始后端响应下添加Access-Control-xx相关header,例如:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, PUT, POST, DELETE, PATCH, OPTIONS
Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range
Access-Control-Allow-Credentials: true
Content-Type: application/json

如果服务器响应中没有正确设置CORS头部(如Access-Control-Allow-Origin),浏览器会阻止前端 JavaScript访问响应数据,这就是所谓的CORS错误发生跨域错误对应的浏览器行为。

  1. 发送预检请求:

    浏览器将CORS请求分成两类:

    • 简单请求(simple request),直接发送请求,不需要发送预检请求。

    • 非简单请求(not-so-simple request)。

    浏览器发送预检请求:

    OPTIONS /data HTTP/1.1
    Host: api.example.com
    Origin: https://frontend.example.com
    Access-Control-Request-Method: POST
    Access-Control-Request-Headers: X-Custom-Header
  2. 预检请求的响应:

    服务器响应预检请求,但未包含POST方法:

    HTTP/1.1 204 No Content
    Access-Control-Allow-Origin: https://frontend.example.com
    Access-Control-Allow-Methods: GET, OPTIONS
    Access-Control-Allow-Headers: X-Custom-Header
  3. 浏览器决定是否发送实际请求:

    • 浏览器检查预检请求的响应,发现Access-Control-Allow-Methods中不包含POST方法。

    • 浏览器会阻止后续的实际POST请求。

    • 控制台会报类似以下的CORS错误:

    Access to fetch at 'https://api.example.com/data' from origin 'https://frontend.example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: xxxx.

    或者

    Method POST is not allowed by Access-Control-Allow-Methods in preflight response

FAQ

在设置了nginx.ingress.kubernetes.io/cors-allow-credentials: "true"后,是否允许将nginx.ingress.kubernetes.io/cors-allow-origin配置为"*"?

不允许。由于浏览器安全策略限制,当Access-Control-Allow-Credentials设置为true时,Access-Control-Allow-Origin不能设为通配符"*"。这一限制旨在防止未经授权的域访问敏感资源,例如携带用户凭证信息的Cookies或者Authorization头部等。

nginx.ingress.kubernetes.io/cors-expose-headersAccess-Control-Expose-Headers HTTP header)的作用对象是什么?

nginx.ingress.kubernetes.io/cors-expose-headers用于设置Access-Control-Expose-Headers 的HTTP响应头,以决定哪些非标准响应头字段可以通过客户端的JavaScript代码获取,例如通过XMLHttpRequestgetResponseHeader()方法获取。

默认情况下,浏览器在处理跨域请求时只能访问标准的响应头,包括Cache-Control、Content-Language、Content-Type、Expires、Last-Modified和Pragma。服务端返回的任何自定义头字段在没有配置的情况下对客户端JavaScript都是不可见的。

通过配置Access-Control-Expose-Headers头部,服务器可以指定哪些自定义响应头字段可以被浏览器暴露给JavaScript。例如,如果希望使自定义头X-Custom-Header在JavaScript中可访问,服务器需在响应中添加以下头部:

Access-Control-Expose-Headers: X-Custom-Header