Cors 在使用Fetch的云函数中未发送/查看授权标头

Cors 在使用Fetch的云函数中未发送/查看授权标头,cors,authorization,google-cloud-functions,fetch,netlify,Cors,Authorization,Google Cloud Functions,Fetch,Netlify,好像我的头撞到墙上了。。。我已经尝试了多种方法来获取一个简单的Fetch调用来发送授权头。您可以在上看到演示 我收到了许多不同的CORS错误,但最终它们都来自于访问控制允许擦除凭据(变成'),或者更简单地说,首先没有授权头 这个设置是一个简单的index.html,托管在Netlify上,后台是一个Google云函数,它应该接收一个令牌,然后将其传回。当然,这是一个更加复杂的设置,但我甚至不能让这个极其简单的版本正常工作。我还通过使用对AWS Lambda函数的近似等效调用验证了该场景,该函数工

好像我的头撞到墙上了。。。我已经尝试了多种方法来获取一个简单的Fetch调用来发送授权头。您可以在上看到演示

我收到了许多不同的CORS错误,但最终它们都来自于访问控制允许擦除凭据(变成
'
),或者更简单地说,首先没有授权头

这个设置是一个简单的
index.html
,托管在Netlify上,后台是一个Google云函数,它应该接收一个令牌,然后将其传回。当然,这是一个更加复杂的设置,但我甚至不能让这个极其简单的版本正常工作。我还通过使用对AWS Lambda函数的近似等效调用验证了该场景,该函数工作得非常好。该函数前面有一个自动生成的API网关,但我已经指定了一些基线CORS设置

如果调用之前提出过选项方法,则调用
res.end()
似乎是一种模式,但我不确定为什么这是一种处理此问题的好方法。尽管如此,我没有得到令牌返回

目前,授权头似乎没有被发送,更不用说在后端接收了

有人看到哪里出了问题吗

请不要建议使用npm
cors
软件包,因为如果没有特定的依赖关系,它不会有帮助,也不会做任何无法显式编程的事情

我发现一个相关的问题正在发生

云功能后端

'use strict';

exports.minimalAuthorization = function(req, res) {
    const ORIGIN = req.headers.origin;
    console.log('ORIGIN', ORIGIN);

    const TOKEN = req.headers.Authorization || req.headers.authorization;
    console.log('TOKEN', TOKEN);

    const METHOD = req.method;
    console.log('METHOD', METHOD);

    if (req.method === 'OPTIONS') {
        res.end();
    } else {
        res.set('Access-Control-Allow-Origin', ORIGIN);
        res.set('Access-Control-Allow-Credentials', 'true');
        res.set('Access-Control-Allow-Methods', '*'); // POST, OPTIONS
        res.set('Access-Control-Allow-Headers', '*'); // Origin, Content-Type, Accept, Authorization, authorization

        if (TOKEN) {
            res.status(200).send(JSON.stringify(TOKEN));
        } else res.status(400).send(JSON.stringify('Sorry, no token for you...'));
    }
};
exports.minimalAuthorization = function(req, res) {
    const TOKEN = req.headers.authorization;
    console.log('TOKEN', TOKEN);

    const METHOD = req.method;
    console.log('METHOD', METHOD);

    res.set('Access-Control-Allow-Origin', 'https://demo-gcf-auth.netlify.com'); // Your origin here
    res.set('Access-Control-Allow-Credentials', 'true');
    res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');

    // A 204 response (preflight response) happens before actually trying to respond, so accept the auth header and send an OK
    if (METHOD === 'OPTIONS') {
        res.set('Access-Control-Allow-Headers', 'Authorization');
        res.status(204).send('');
    } else {
        // If it's not an OPTIONS request, actually do send the value/token back
        if (TOKEN) {
            res.status(200).send(JSON.stringify(TOKEN));
        } else res.status(400).send(JSON.stringify('Sorry, no token for you...'));
    }
};

相关HTML脚本部分

<script>
    const ENDPOINT =
        'https://europe-west1-cloud-developer-basics.cloudfunctions.net/minimalAuthorization';
    const TOKEN = `eyJhbGciOiJSUzI1NiIsImtpZCI6ImEwYjQwY2NjYmQ0OWQxNmVkMjg2MGRiNzIyNmQ3NDZiNmZhZmRmYzAiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiTWlrYWVsIFZlc2F2dW9yaSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQUF1RTdtQVhELWtPZHpKbDVDMF9ad3JLY3A3Q2VWWmQzQlp3eG5ydzlicUFTd1UiLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vY2xvdWQtZGV2ZWxvcGVyLWJhc2ljcy1lZmUzOSIsImF1ZCI6ImNsb3VkLWRldmVsb3Blci1iYXNpY3MtZWZlMzkiLCJhdXRoX3RpbWUiOjE1NzIyOTc0NTksInVzZXJfaWQiOiJwVERCelM0ZDVyWnJqYjlvMFZLa3g3YmtTZnYyIiwic3ViIjoicFREQnpTNGQ1clpyamI5bzBWS2t4N2JrU2Z2MiIsImlhdCI6MTU3MjI5NzQ2MCwiZXhwIjoxNTcyMzAxMDYwLCJlbWFpbCI6Im1pa2FlbHZlc2F2dW9yaUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjEwNzUwNDM4MzUwMTA4NDY5ODEzNCJdLCJlbWFpbCI6WyJtaWthZWx2ZXNhdnVvcmlAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoiZ29vZ2xlLmNvbSJ9fQ.LI2ySD6uafnkruEDDmkym6JKoMdhjOEOGKQiytc3SFyeCDERwylqwsmiaCtE7Q6W_FjqrNaAW2rV09rcvuQFPGAMA8uSGiUCdlwau1tBIENHu_HGdW4wI_PWEi6sRmIpbMPTsIPjpcmsSIcpd_WDtz4EldAboXkottFSS7dU81MDbdgrdwKyaq8y-haJqBtr2LAIHy5rg7leSXyY9wqmj9u4iwExWn-pY6BK7dGCEJFTK0_Czvs3qi-0e8bEPmUXwiKzuuMIL_B9l22EHZXqJv0nd9LIzN5_ofyv63U2rG4DbTgNupRAeibhxUO5djVNtgCcFV49618t9ca81d7znQ`;

    async function callApiWithToken(token) {
        console.log('Calling API with token:', token);

        await fetch(ENDPOINT, {
            method: 'POST',
            credentials: 'include',
            headers: {
                Authorization: `Bearer ${token}`
            }
        })
            .then(res => res.json())
            .catch(error => {
                console.error(error);
            });
    }

    callApiWithToken(TOKEN);
</script>
无服务器功能配置,AWS Lambda

functions:
  minimalAuthorization:
    handler: functions/minimalAuthorization.handler
    events:
      - http:
          method: GET
          path: minimalAuthorization
          cors:
            origin: 'https://demo-gcf-auth.netlify.com'
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
            allowCredentials: true

我有点困惑,为什么后端代码将origin设置为请求的origin头。我猜这是出于测试目的,但这意味着所有请求都是有效的

const ORIGIN = req.headers.origin;
这将与设置相同

const ORIGIN = '*';
但是,我通过一些调整成功地使您的代码正常工作:

'use strict';

exports.minimalAuthorization = function(req, res) {
    const ORIGIN = req.headers.origin;
    console.log('ORIGIN', ORIGIN);

    const TOKEN = req.headers.authorization;
    console.log('TOKEN', TOKEN);

    const METHOD = req.method;
    console.log('METHOD', METHOD);

    res.set('Access-Control-Allow-Origin', ORIGIN);
    res.set('Access-Control-Allow-Credentials', 'true');
    res.set('Access-Control-Allow-Methods', '*'); // POST, OPTIONS

    if (req.method === 'OPTIONS') {
        res.set('Access-Control-Allow-Headers', 'Authorization');
        res.status(204).send('');
    } else {
        if (TOKEN) {
            res.status(200).send(JSON.stringify(TOKEN));
        } else res.status(400).send(JSON.stringify('Sorry, no token for you...'));
    }
};
我认为OPTION方法需要返回2xx响应才能通过预飞

此外,我还将一些标题(如“Access Control Allow Origin”)从非选项响应中移出,并将“Access Control Allow headers”放入选项响应中

我还在codepen上创建了一个前端来测试该功能:
哇,这本应该更加明显。在不同的方面,我看到了始终显式设置内容类型的建议(始终使用
application/json
)的值。这是完全错误的,应该删除,或者将内容类型设置为,例如,
text/plain
。这记录在

不太清楚为什么相同的实际HTML会使用AWS Lambda提供功能正常的204+200响应

结束。如果您坚持使用CORS,我将提供前端和后端示例,以便在您需要授权头时为您实现这一点

front

'use strict';

function minimalAuthorization(event, context) {
    const TOKEN = event.headers.Authorization.split('Bearer ')[1];
    console.log('TOKEN', TOKEN);

    const ORIGIN = event.headers.origin;
    console.log('ORIGIN', ORIGIN);

    if (TOKEN) {
        return {
            statusCode: 200,
            body: TOKEN,
            headers: {
                'Access-Control-Allow-Origin': ORIGIN,
                'Access-Control-Allow-Credentials': true
            }
        };
    } else
        return {
            statusCode: 400,
            body: 'Sorry, no token for you...',
            headers: {
                'Access-Control-Allow-Origin': ORIGIN,
                'Access-Control-Allow-Credentials': true
            }
        };
}

exports.handler = async (event, context) => {
    return minimalAuthorization(event, context);
};
// CORS mode is default, as is Content-Type: 'text/plain'; both are required
await fetch(ENDPOINT, {
    credentials: 'include',
    headers: {
        Authorization: `Bearer ${token}`
    }
})
    .then(res => res.text())
    .then(data => console.log(data))
    .catch(error => {
        console.error(error);
    });
后端

'use strict';

exports.minimalAuthorization = function(req, res) {
    const ORIGIN = req.headers.origin;
    console.log('ORIGIN', ORIGIN);

    const TOKEN = req.headers.Authorization || req.headers.authorization;
    console.log('TOKEN', TOKEN);

    const METHOD = req.method;
    console.log('METHOD', METHOD);

    if (req.method === 'OPTIONS') {
        res.end();
    } else {
        res.set('Access-Control-Allow-Origin', ORIGIN);
        res.set('Access-Control-Allow-Credentials', 'true');
        res.set('Access-Control-Allow-Methods', '*'); // POST, OPTIONS
        res.set('Access-Control-Allow-Headers', '*'); // Origin, Content-Type, Accept, Authorization, authorization

        if (TOKEN) {
            res.status(200).send(JSON.stringify(TOKEN));
        } else res.status(400).send(JSON.stringify('Sorry, no token for you...'));
    }
};
exports.minimalAuthorization = function(req, res) {
    const TOKEN = req.headers.authorization;
    console.log('TOKEN', TOKEN);

    const METHOD = req.method;
    console.log('METHOD', METHOD);

    res.set('Access-Control-Allow-Origin', 'https://demo-gcf-auth.netlify.com'); // Your origin here
    res.set('Access-Control-Allow-Credentials', 'true');
    res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');

    // A 204 response (preflight response) happens before actually trying to respond, so accept the auth header and send an OK
    if (METHOD === 'OPTIONS') {
        res.set('Access-Control-Allow-Headers', 'Authorization');
        res.status(204).send('');
    } else {
        // If it's not an OPTIONS request, actually do send the value/token back
        if (TOKEN) {
            res.status(200).send(JSON.stringify(TOKEN));
        } else res.status(400).send(JSON.stringify('Sorry, no token for you...'));
    }
};


使用浏览器devtools中的网络窗格进行检查表明浏览器正在向
https://europe-west1-cloud-developer-basics.cloudfunctions.net/minimalAuthorization
但该预飞失败,因为响应不包括访问控制允许原点响应标头。因此,您当前的服务器代码似乎没有正确处理选项请求-至少它没有导致在对该选项的响应中发送Access Control Allow Origin标头,直到授权请求标头为止,预计浏览器不会将其包含在飞行前选项请求中,因为CORS规范要求浏览器忽略它(无论如何,在该选项请求中,浏览器不会包含您在前端代码中设置的任何标题)。
就授权请求标题而言,预计浏览器不会在飞行前选项请求中包含这一点[…]
对我来说是有趣和令人惊讶的,因为(据我所知)如果它是一个选项请求,根据
if(req.method=='OPTIONS'){//Send response to OPTIONS requests res.set准确地发送auth头('Access-Control-Allow-Methods','GET');res.set('Access-Control-Allow-Headers','Authorization');[…]
?授权请求标头不会在选项请求中发送。谷歌的示例显示了用于处理选项请求响应的服务器端代码。它不会显示正在发送的授权标头,而是显示了配置为响应选项请求的服务器,例如,“我将允许包含授权标头的请求”。这就是
访问控制允许标题:授权
响应标题的含义。请检查!正如我自己的回答所示,授权标题没有发送,因为内容类型错误。我看到您使用的是谷歌在上展示的参考实现。我已经尝试过了,现在也尝试过了。但是,使用same结果。日志根本不显示令牌。此外,我只得到了成功的204响应,但没有像您在演示中那样得到200响应。您可以尝试在Netlify托管HTML吗?我开始强烈怀疑Netlify不起作用。关于
res.set('Access-Control-Allow-Origin',Origin);
这在功能上与使用
“*”
相同,但是当您发送凭据时,星号(AFAIK)会被列入黑名单,并且总是失败。因此,您将其设置为源站时会得到相同的实际结果。这仍然是一种懒惰的方法,但对于测试它应该可以完成这项工作。