关于javascript:当尝试从REST API获取数据时,请求的资源上没有“Access-Control-Allow-Origin”标头

No 'Access-Control-Allow-Origin' header is present on the requested resource—when trying to get data from a REST API

我正在尝试从HP Alm的REST API中获取一些数据。 它适用于小卷曲脚本 - 我得到了我的数据。

现在使用JavaScript,fetch和ES6(或多或少)似乎是一个更大的问题。 我一直收到此错误消息:

Fetch API cannot load . Response to preflight request doesn't
pass access control check: No 'Access-Control-Allow-Origin' header is
present on the requested resource. Origin 'http://127.0.0.1:3000' is
therefore not allowed access. The response had HTTP status code 501.
If an opaque response serves your needs, set the request's mode to
'no-cors' to fetch the resource with CORS disabled.

我知道这是因为我试图从我的localhost中获取该数据,解决方案应该使用CORS。 现在我以为我确实这样做了,但不管怎么说它要么忽略我在标题中写的内容,要么问题是别的什么?

那么,是否存在实施问题? 我做错了吗? 遗憾的是我无法检查服务器日志。 我真的有点卡在这里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function performSignIn() {

  let headers = new Headers();

  headers.append('Content-Type', 'application/json');
  headers.append('Accept', 'application/json');

  headers.append('Access-Control-Allow-Origin', 'http://localhost:3000');
  headers.append('Access-Control-Allow-Credentials', 'true');

  headers.append('GET', 'POST', 'OPTIONS');

  headers.append('Authorization', 'Basic ' + base64.encode(username +":" + password));

  fetch(sign_in, {
      //mode: 'no-cors',
      credentials: 'include',
      method: 'POST',
      headers: headers
    })
    .then(response => response.json())
    .then(json => console.log(json))
    .catch(error => console.log('Authorization failed : ' + error.message));
}

我正在使用Chrome。 我也尝试使用Chrome CORS插件,但后来我收到另一条错误消息:

The value of the 'Access-Control-Allow-Origin' header in the response
must not be the wildcard '*' when the request's credentials mode is
'include'. Origin 'http://127.0.0.1:3000' is therefore not allowed
access. The credentials mode of requests initiated by the
XMLHttpRequest is controlled by the withCredentials attribute.


这个答案涵盖了很多方面,因此它分为三个部分:

  • 如何使用CORS代理来解决"No Access-Control-Allow-Origin header"问题
  • 如何避免CORS预检
  • 如何修复"Access-Control-Allow-Origin标头不能是通配符"问题
  • 如何使用CORS代理来解决"No Access-Control-Allow-Origin header"问题

    如果您不控制服务器,您的前端JavaScript代码正在向其发送请求,而该服务器响应的问题只是缺少必要的Access-Control-Allow-Origin标头,您仍然可以通过制作请求通过CORS代理。为了说明它是如何工作的,首先是一些不使用CORS代理的代码:

    1
    2
    3
    4
    5
    const url ="https://example.com"; // site that doesn’t send Access-Control-*
    fetch(url)
    .then(response => response.text())
    .then(contents => console.log(contents))
    .catch(() => console.log("Can’t access" + url +" response. Blocked by browser?"))

    catch块被击中的原因是,浏览器阻止该代码访问从https://example.com返回的响应。浏览器这样做的原因是,响应缺少Access-Control-Allow-Origin响应头。

    现在,这是完全相同的示例,但只是添加了一个CORS代理:

    1
    2
    3
    4
    5
    6
    const proxyurl ="https://cors-anywhere.herokuapp.com/";
    const url ="https://example.com"; // site that doesn’t send Access-Control-*
    fetch(proxyurl + url) // https://cors-anywhere.herokuapp.com/https://example.com
    .then(response => response.text())
    .then(contents => console.log(contents))
    .catch(() => console.log("Can’t access" + url +" response. Blocked by browser?"))

    注意:如果https://cors-anywhere.herokuapp.com在您尝试时关闭或不可用,请参阅下文,了解如何在仅仅2-3分钟内在Heroku部署您自己的CORS Anywhere服务器。

    上面的第二个代码段可以成功访问响应,因为获取请求URL并将其更改为https://cors-anywhere.herokuapp.com/https://example.com-只需在其前面加上代理URL - 导致请求通过该代理进行,然后:

  • 将请求转发到https://example.com
  • 收到https://example.com的回复。
  • Access-Control-Allow-Origin标头添加到响应中。
  • 通过添加的标头将响应传递回请求的前端代码。
  • 然后,浏览器允许前端代码访问响应,因为带有Access-Control-Allow-Origin响应头的响应是浏览器看到的。

    您可以使用https://github.com/Rob--W/cors-anywhere/中的代码轻松运行自己的代理。
    您还可以使用5个命令轻松地将自己的代理部署到Heroku,只需2-3分钟:

    1
    2
    3
    4
    5
    git clone https://github.com/Rob--W/cors-anywhere.git
    cd cors-anywhere/
    npm install
    heroku create
    git push heroku master

    运行这些命令后,您最终将拥有自己运行的CORS Anywhere服务器,例如https://cryptic-headland-94862.herokuapp.com/。因此,不要在您的请求URL前加上https://cors-anywhere.herokuapp.com前缀,而是使用您自己实例的URL作为前缀;例如,https://cryptic-headland-94862.herokuapp.com/https://example.com。

    因此,如果你去尝试使用https://cors-anywhere.herokuapp.com,你会发现它已经失效(有时会是这样),然后考虑获得一个Heroku帐户(如果你还没有)并采取2或3分钟完成上述步骤,在Heroku上部署自己的CORS Anywhere服务器。

    无论您是运行自己的还是使用https://cors-anywhere.herokuapp.com或其他开放代理,即使请求是触发浏览器执行CORS预检OPTIONS请求的解决方案,此解决方案也会起作用 - 因为在这种情况下,代理还会发送回预检成功所需的Access-Control-Allow-HeadersAccess-Control-Allow-Methods标头。

    如何避免CORS预检

    问题中的代码触发CORS预检 - 因为它发送Authorization标头。

    https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests

    即使没有它,Content-Type: application/json标题也会触发预检。

    什么"预检"意味着:在浏览器尝试问题代码中的POST之前,它首先向服务器发送OPTIONS请求,以确定服务器是否选择接收交叉来源POST,包括AuthorizationContent-Type: application/json标头。

    It works pretty well with a small curl script - I get my data.

    Ok.

    要使用curl正确测试,您需要模拟浏览器发送的预检OPTIONS请求:

    1
    2
    3
    4
    curl -i -X OPTIONS -H"Origin: http://127.0.0.1:3000" \
        -H 'Access-Control-Request-Method: POST' \
        -H 'Access-Control-Request-Headers: Content-Type, Authorization' \
       "https://the.sign_in.url"

    ... https://the.sign_in.url替换为您的实际sign_in URL。

    浏览器需要从该OPTIONS请求中看到的响应必须包含以下标头:

    1
    2
    3
    Access-Control-Allow-Origin:  http://127.0.0.1:3000
    Access-Control-Allow-Methods: POST
    Access-Control-Allow-Headers: Content-Type, Authorization

    如果OPTIONS响应不包含那些标头,那么浏览器将在那里停止,甚至从未尝试发送POST请求。此外,响应的HTTP状态代码必须是2xx - 通常为200或204.如果是任何其他状态代码,浏览器将在那里停止。

    问题中的服务器使用501状态代码响应OPTIONS请求,这显然意味着它正在尝试表明它没有实现对OPTIONS请求的支持。在这种情况下,其他服务器通常以405"方法不允许"状态代码进行响应。

    因此,如果服务器使用405或501或200或204以外的任何其他任何内容响应该OPTIONS请求,或者如果没有,则您永远无法从前端JavaScript代码直接向该服务器发出POST请求不回应那些必要的响应标题。

    避免在问题中触发案件预检的方法是:

  • 如果服务器不需要Authorization请求标头,而是(例如)依赖于嵌入在POST请求主体中的身份验证数据或作为查询参数
  • 如果服务器不需要POST主体具有Content-Type: application/json媒体类型,而是接受POST主体为application/x-www-form-urlencoded,其参数名为json(或其他),其值为JSON数据
  • 如何修复"Access-Control-Allow-Origin标头不能是通配符"问题

    我收到另一条错误消息:

    The value of the 'Access-Control-Allow-Origin' header in the response
    must not be the wildcard '*' when the request's credentials mode is
    'include'. Origin 'http://127.0.0.1:3000' is therefore not allowed
    access. The credentials mode of requests initiated by the
    XMLHttpRequest is controlled by the withCredentials attribute.

    Ok.

    对于包含凭据的请求,如果Access-Control-Allow-Origin响应标头的值为*,浏览器将不允许您的前端JavaScript代码访问响应。相反,该情况下的值必须与您的前端代码的原点http://127.0.0.1:3000完全匹配。

    请参阅MDN HTTP访问控制(CORS)文章中的Credentialed请求和通配符。

    如果您控制要发送请求的服务器,那么处理这种情况的常用方法是配置服务器以获取Origin请求标头的值,并将其回显/反映回该值的值Access-Control-Allow-Origin响应标头。例如,使用nginx:

    1
    add_header Access-Control-Allow-Origin $http_origin

    但这只是一个例子;其他(Web)服务器系统提供类似的方式来回显原始值。

    I am using Chrome. I also tried using that Chrome CORS Plugin

    Ok.

    Chrome CORS插件显然只是简单地在浏览器看到的响应中注入Access-Control-Allow-Origin: *标头。如果插件更聪明,它将要做的是将伪Access-Control-Allow-Origin响应头的值设置为前端JavaScript代码http://127.0.0.1:3000的实际原点。

    因此,即使是测试,也要避免使用该插件。这只是一种分心。如果您想测试从服务器获得的响应而没有浏览器过滤它们,那么最好使用上面的curl -H

    至于问题中fetch(…)请求的前端JavaScript代码:

    1
    2
    headers.append('Access-Control-Allow-Origin', 'http://localhost:3000');
    headers.append('Access-Control-Allow-Credentials', 'true');

    删除这些行。 Access-Control-Allow-*标头是响应标头。您永远不想在请求中发送它们。唯一的影响是触发浏览器进行预检。

    好。


    当客户端URL和服务器URL不匹配(包括端口号)时,会发生此错误。在这种情况下,您需要为CORS启用服务,这是跨源资源共享。

    如果您正在托管Spring REST服务,那么您可以在Spring Framework中的CORS支持的博客文章中找到它。

    如果您使用Node.js服务器托管服务,那么

  • 停止Node.js服务器。
  • npm install cors --save
  • 将以下行添加到server.js

    1
    2
    3
    var cors = require('cors')

    app.use(cors()) // Use this after the variable declaration

  • 出现问题的原因是您在前端添加了以下代码作为请求标头:

    1
    2
    headers.append('Access-Control-Allow-Origin', 'http://localhost:3000');
    headers.append('Access-Control-Allow-Credentials', 'true');

    那些标题属于响应,而不是请求。所以删除它们,包括行:

    1
    headers.append('GET', 'POST', 'OPTIONS');

    您的请求有'Content-Type:application / json',因此触发了所谓的CORS预检。这导致浏览器使用OPTIONS方法发送请求。有关详细信息,请参阅CORS预检。

    因此,在后端,您必须通过返回响应标头来处理此预检请求,其中包括:

    1
    2
    3
    4
    Access-Control-Allow-Origin : http://localhost:3000
    Access-Control-Allow-Credentials : true
    Access-Control-Allow-Methods : GET, POST, OPTIONS
    Access-Control-Allow-Headers : Origin, Content-Type, Accept

    当然,实际语法取决于您用于后端的编程语言。

    在你的前端,它应该是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function performSignIn() {
        let headers = new Headers();

        headers.append('Content-Type', 'application/json');
        headers.append('Accept', 'application/json');
        headers.append('Authorization', 'Basic ' + base64.encode(username +":" +  password));
        headers.append('Origin','http://localhost:3000');

        fetch(sign_in, {
            mode: 'cors',
            credentials: 'include',
            method: 'POST',
            headers: headers
        })
        .then(response => response.json())
        .then(json => console.log(json))
        .catch(error => console.log('Authorization failed : ' + error.message));
    }


    就我而言,Web服务器阻止了"OPTIONS"方法

    检查Web服务器以获取选项方法

    • apache:https://www-01.ibm.com/support/docview.wss?uid = ibm10735209
    • webtier:4.4.6禁用选项方法https://docs.oracle.com/cd/E23943_01/web.1111/e10144/getstart.htm#HSADM174
    • nginx:https://medium.com/@hariomvashisth/cors-on-nginx-be38dd0e19df

    I'm using"webtier"

    /www/webtier/domains/[domainname]/config/fmwconfig/components/OHS/VCWeb1/httpd.conf

    1
    2
    3
    4
    5
    <IfModule mod_rewrite.c>
      RewriteEngine on
      RewriteCond %{REQUEST_METHOD} ^OPTIONS
      RewriteRule .* . [F]
    </IfModule>

    改成

    1
    2
    3
    4
    5
    <IfModule mod_rewrite.c>
      RewriteEngine off
      RewriteCond %{REQUEST_METHOD} ^OPTIONS
      RewriteRule .* . [F]
    </IfModule>

    只是我的两分钱......关于如何使用CORS代理来解决"No Access-Control-Allow-Origin header"问题

    对于那些在后端使用php的人来说,部署"CORS代理"就像这样简单:

  • 使用以下内容创建名为"no-cors.php"的文件:

    $URL = $_GET['url'];
    echo json_encode(file_get_contents($URL));
    die();

  • 在你的前端,做一些像:

    fetch('https://example.com/no-cors.php' + '?url=' + url)
    .then(response=>{*/Handle Response/*})


  • 我正在使用Spring REST,我解决了将AllowedMethods添加到WebMvcConfigurer中的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Value("${app.allow.origins}" )
        private String allowOrigins;    
    @Bean
    public WebMvcConfigurer corsConfigurer() {
                System.out.println("allow origin:"+allowOrigins);
                return new WebMvcConfigurerAdapter() {
                    @Override
                    public void addCorsMappings(CorsRegistry registry) {
                        registry.addMapping("/**")
                        //.allowedOrigins("http://localhost")
                        .allowedOrigins(allowOrigins)
                        .allowedMethods("PUT","DELETE","GET","POST");
                    }
                };
            }


    使用dataType: 'jsonp'为我工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
       async function get_ajax_data(){
           var _reprojected_lat_lng = await $.ajax({
                                    type: 'GET',
                                    dataType: 'jsonp',
                                    data: {},
                                    url: _reprojection_url,
                                    error: function (jqXHR, textStatus, errorThrown) {
                                        console.log(jqXHR)
                                    },
                                    success: function (data) {
                                        console.log(data);

                                        // note: data is already json type, you
                                        //       just specify dataType: jsonp
                                        return data;
                                    }
                                });


     } // function

    删除这个:

    1
    credentials: 'include',