logo
Published on

为什么我的JavaScript代码会收到“No 'Access-Control-Allow-Origin' header is present on the requested resource”错误,而Postman不会?

Authors
  • Name
    Twitter

在本文中,我们将探讨为什么您的JavaScript代码在浏览器中请求API资源时会遇到“No 'Access-Control-Allow-Origin' header is present on the requested resource”错误,而同样的请求在Postman中不会遇到该问题。

深入探究

在以下的调查中,我们使用http://example.com代替http://myApiUrl/login进行演示,因为该地址是可用的。我假设您的页面在[http://my-site.local:8088](http://my-site.local:8088/)。

注意:API和您的页面拥有不同的域名!

之所以会出现不同的结果,是因为Postman的请求方式不同于浏览器:

  • Postman设置了Host=example.com(您的API)
  • Postman 没有设置Origin头信息
  • Postman实际上不会使用您的网站URL(您只需在Postman中输入API地址)——Postman仅发送请求到API,所以它假定网站与API拥有相同地址(浏览器则不做此假定)

这类似于当站点和API拥有相同域名时浏览器发送请求的方式(浏览器还会设置Referer=http://my-site.local:8088,然而在Postman中看不到这点)。Origin头信息未设置时,通常服务器会默认允许这些请求。

Image 1

这是Postman发送请求的标准方式。但当您的站点和API拥有不同域名时,浏览器发送请求的方式不同,这时会触发CORS,浏览器自动进行如下操作:

  • 设置Host=example.com(您的API)
  • 设置Origin=http://my-site.local:8088(您的站点)

Referer头信息与Origin具有相同的值)。现在,您可以在Chrome的_Console & Network_选项卡中看到如下信息:

Image 2

Image 3

当您具有**Host != Origin**时,就会触发CORS,此时服务器通常会默认拦截请求。

当从本地目录打开HTML内容并发送请求时,Origin=null会被设置。在在<iframe>中发送请求时也会遇到相同情况(此时Host头信息完全没有设置)——一般来说,HTML规范中提到的不透明来源,可以理解为Origin=null。更多信息可参考这里

fetch('http://example.com/api', { method: 'POST' });
查看Chrome控制台 > 网络选项卡

如果您不使用简单的CORS请求,通常浏览器会在发送主请求前自动发送一个OPTIONS预检请求——更多信息在这里。下面的代码片段展示了这一点:

fetch('http://example.com/api', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }
});
查看Chrome控制台 -> 网络选项卡的'api'请求。
这是OPTIONS预检请求 (服务器不允许发送POST请求)

您可以修改服务器的配置以允许CORS请求。

以下是启用nginx上的CORS的示例配置(nginx.conf文件)——对设置always/"$http_origin"(nginx)和"*"(Apache)要非常小心——这将允许来自任何域的CORS请求(在生产环境中使用具体的页面地址而非星号以使用您的API)。

以下是启用Apache上的CORS的示例配置(.htaccess文件)。

# Apache example
<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "*"
    Header set Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
</IfModule>
# Nginx example
server {
    location / {
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' "$http_origin" always;
            add_header 'Access-Control-Allow-Credentials' 'true';
            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-Expose-Headers' 'Content-Length,Content-Range';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        if ($request_method = 'POST') {
            add_header 'Access-Control-Allow-Origin' "$http_origin" always;
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
        }

        # handle other requests normally
        proxy_pass http://backend_server;
    }
}

确保您的API可以与您的网页进行正确交互,前提是配置文件设置正确且符合安全要求。