跨域

Last Updated on 2024-07-26 by likun.gong

跨域是什么

跨域只发生在浏览器的访问中,发生跨域时,通常会看到 CORS error 等字眼,并且请求返回了 403 状态码。

为什么会跨域

在理解跨域之前,要先知道浏览器中的同源策略。

同源策略:是指两个页面具有相同的协议、地址、端口,那么它们就是同源,只要有一个不同,就不是同源。不同源之间的访问,就会产生跨域。

例如 https://a.com 页面里边的 JavaScript 脚本去访问 https://b.com ,就会产生跨域的情况。

同源策略的目的是为了保护用户隐私和数据安全,但是我们还是有需要进行合法的跨域访问的。比如我们的图片资源都是存在一个独立的 CDN 资源中,其他业务都统一来这个域名访问资源,这之间就会产生跨域的情况。

如何解决跨域

目前最常见解决跨域的办法,就是通过 CORS(跨域资源共享)。

CORS(跨域资源共享):通过设置HTTP头来允许跨域请求。

在上述的例子中,a.com 如何知道自己能够跨域访问 b.com 呢?它会在发起 GET、POST 等这些请求前,先发起一个 preflight 的请求,也就是 OPTIONS 请求,根据返回判断 b.com 是否允许自己访问。

需要注意的一点是,并不是所有的跨域都会发起 preflight 请求,同时满足以下情况是不会发起 OPTIONS 请求的:

  1. 请求方法是 GET、POST、HEAD
  2. 请求中没有特殊的请求头

跨域主要涉及的请求头信息:

  • Access-Control-Allow-Origin 用于设置允许跨域请求源地址 (预检请求和正式请求在跨域时候都会验证)
  • Access-Control-Allow-Headers 跨域允许携带的特殊头信息字段 (只在预检请求验证)
  • Access-Control-Allow-Methods 跨域允许的请求方法或者说HTTP动词 (只在预检请求验证)
  • Access-Control-Allow-Credentials 是否允许跨域使用cookies,如果要跨域使用cookies,可以添加上此请求响应头,值设为true(设置或者不设置,都不会影响请求发送,只会影响在跨域时候是否要携带cookies,但是如果设置,预检请求和正式请求都需要设置)

安全起见,Access-Control-Allow-Origin 一般不会设置 * 对所有开放,而是指定域名开放

Nginx中解决跨域

在 Nginx 的配置中,需要处理 OPTIONS 请求,同时对于 OPTIONS 之后的请求也需要添加对应的响应头

server {
    listen 80;
    server_name example.com;

    location / {
        # 允许来自任何源的请求
        add_header 'Access-Control-Allow-Origin' '*';

        # 允许的请求方法
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';

        # 允许的请求头
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';

        # 允许浏览器缓存预检请求结果的时间(以秒为单位)
        add_header 'Access-Control-Max-Age' 1728000;

        # 允许浏览器暴露的响应头
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';

        # 处理 OPTIONS 预检请求
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        # 其他配置...
    }
}

Cloudflare中解决跨域

在 cloudflare 中,可以通过配置规则来增加跨域的响应头,但是规则无法处理 OPTIONS 请求。

这里我使用 cloudflare worker 来解决,它类似一个代理,通过 serverless 运行,免费版每天有 10w 次请求

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  // 允许的 origin 列表
  const allowedOrigins = ['https://a.xxx.com','https://b.xx.com','https://c.xxx.com', 'http://172.19.59.219:8080']

  // 获取请求的 origin
  const requestOrigin = request.headers.get('Origin')

  // 检查是否为允许的 origin
  const isAllowedOrigin = allowedOrigins.includes(requestOrigin)

  // 如果是 OPTIONS 请求
  if (request.method === 'OPTIONS') {
      // 创建响应头
      let headers = new Headers({
          'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
          'Access-Control-Allow-Headers': 'x-url-path,content-type,x-user-token,x-language,x-job-number,x-requested-with,eagleeye-sessionid,eagleeye-pappname,eagleeye-traceid,x-data-display-token,preview-token,x-forum-token,X-Mainland,x-equipment-code',
          'Access-Control-Max-Age': '86400', // 24 小时
      })

      // 如果是允许的 origin,设置 Access-Control-Allow-Origin
      if (isAllowedOrigin) {
          headers.set('Access-Control-Allow-Origin', requestOrigin)
      }

      // 返回 response
      return new Response(null, {
          status: 204,
          headers: headers
      })
  }

  // 对于非 OPTIONS 请求,继续正常处理
  // 这里我们只是简单地返回原始响应,您可以根据需要修改
  let response = await fetch(request)

  // 如果是允许的 origin,为非 OPTIONS 请求也添加 CORS 头
  if (isAllowedOrigin) {
      response = new Response(response.body, response)
      response.headers.set('Access-Control-Allow-Origin', requestOrigin)
  }

  return response
}

worker 启动之后,可以配置对应哪些域名、哪些 url 走 worker。

目前 cloudflare 新版出了个 snippets ,看起来像是 worker 的简化版,也能做到同样的事情。


评论

发表回复

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