一篇文章带你解决跨域问题

150 阅读2分钟

什么是跨域

同源(即在同一个域)就是两个页面具有相同的 协议主机端口

跨域 当一个请求URL的 协议域名端口 三者之间的任意一个与 当前页面 url不同即为跨域

  'Access-Control-Allow-Origin': 'http://127.0.0.1:3010',
  'Access-Control-Allow-Headers': 'Test-CORS, Content-Type',
  'Access-Control-Allow-Methods': 'PUT,DELETE',
  'Access-Control-Max-Age': 86400

跨域产生的原因

出于浏览器同源策略限制。

同源策略(Same Orgin Policy)是一种约定,它是浏览器核心也最基本的安全功能,它会阻止一个域的js脚本和另外一个域的内容进行交互。

如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。

非同源会出现的限制

  • 无法读取非同源网页的cookie、local storage等
  • 无法接触非同源网页的DOM和js对象
  • 无法向非同源地址发送Ajax请求

什么是CORS

Cross-origin Resource Sharing 中文名称 “跨域资源共享” 简称 “CORS”,它突破了一个请求在浏览器发出只能在同源的情况下向服务器获取数据的限制。

是浏览器还是服务器的限制

通过一个测试来分析下

image.png 左侧请求的 127.0.0.1:3011/api/data 接口,在请求头里可以看到有 Origin 字段,显示了我们当前的请求信息。另外还有三个 Sec-Fetch-* 开头的字段,这是一个新的草案 Fetch Metadata Request Headers[1]

  • Sec-Fetch-Mode 表示请求的模式,通过左右两侧结果对比也可以看出左侧是跨域的。
  • Sec-Fetch-Site 表示的是这个请求是同源还是跨域。 由于我们这两个请求都是由 3010 端口发出去请求 3011 端口,是不符合同源策略的。

看下浏览器 Console 下的日志信息,根据提示得知原因是从 “http://127.0.0.1:3010” 访问 “http://127.0.0.1:3011/api/data” 被 CORS 策略阻止了,没有 “Access-Control-Allow-Origin” 标头。

image.png

我们也可以在终端通过 curl 命令测试下,在终端脱离浏览器环境也是可以正常请求的。

$ curl http://127.0.0.1:3011/api/data
ok!

结论CORS 是浏览器端的限制,不是服务器端的限制。

预检请求

预检请求 是在发送实际的请求之前,客户端(浏览器)会先发送一个 OPTIONS 方法的请求向服务器确认,如果通过之后,浏览器才会发起真正的请求,这样可以避免跨域请求对服务器的用户数据造成影响。

看到这里你可能有疑问为什么上面的示例没有预检请求?因为 CORS 将请求分为了两类:

  • 简单请求
  • 非简单请求

简单请求 不会触发 CORS 预检请求(浏览器发送options请求),其他都属于 非简单请求

请求方法请求头 - key请求头 - value
GET
POST
HEAD
Content-Typetext/plain
multipart/form-data
application/x-www-form-urlencoded

除了简单请求外都是 复杂请求
如果请求头的 Content-Typeapplication/json 就会触发 CORS 预检请求。

解决跨域问题的方法

1、请求头中设置相应字段

例如在请求中我们可以设置响应头部字段:

  • Access-Control-Allow-Origin
  • Access-Control-Expose-Headers
  • Access-Control-Allow-Methods
  • Access-Control-Max-Age

2、JSONP

浏览器是允许像 link、img、script 标签在路径上加载一些内容进行请求,是允许跨域的,那么 jsonp 的实现原理就是在 script 标签里面加载了一个链接,去访问服务器的某个请求,返回内容。

<body>
  <script>
    // fetch('http://127.0.0.1:3011/api/data', {
    //   method: 'PUT',
    //   headers: {
    //     'Content-Type': 'application/json',
    //     'Test-Cors': 'abc',
    //   },
    //   credentials: "include"
    // });
    <srcipt src="http://127.0.0.1:3011/api/data"></srcipt>
  </script>
</body>

注意:JSONP 只支持 GET 请求。

3、Nginx 代理服务器配置跨域

使用 Nginx 代理服务器之后,请求不会直接到达我们的服务器端,请求会先经过 Nginx 在设置一些跨域等信息之后再由 Nginx 转发到我们的服务端,所以这个时候我们的 Nginx 服务器去监听的 3011 端口,我们把服务的端口修改为 30011,简单配置如下所示:

server {
  listen          3011;
  server_name     localhost;

  location / {
    if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Allow-Origin' 'http://127.0.0.1:3010';
      add_header 'Access-Control-Allow-Methods' 'PUT,DELETE';
      add_header 'Access-Control-Allow-Headers' 'Test-CORS, Content-Type';
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Access-Control-Allow-Credentials' 'true';
      add_header 'Content-Length' 0;
      return 204;
    }

    add_header 'Access-Control-Allow-Origin' 'http://127.0.0.1:3010';
    add_header 'Access-Control-Allow-Credentials' 'true';

    proxy_pass http://127.0.0.1:30011;
    proxy_set_header Host $host;
  }
}

总结

如果你是一个前端开发者,在工作难免会遇到跨域问题,虽然它属于浏览器的同源策略限制,但是要想解决这问题还需浏览器端与服务端的共同支持,希望读到这篇文章的读者能够理解跨域产生的原因,最后给予的几个解决方案,也希望能解决你对于跨域这个问题的困惑。