AWS Cloudfront 在发送到源之前删除特定的 cookie

AWS Cloudfront remove particular cookie before sending to origin

我想在发送到原始服务器之前删除 aws cloudfront 中的特定 cookie。 除了名为 "_x_ad_zone".

的 cookie 之外,我必须将所有 cookie 发送到 origin

我在云前端配置中找不到任何删除特定 cookie 的选项。我相信我们必须用 lambda 来实现,但我不知道该怎么做。

请告诉我怎样才能达到同样的效果。

[编辑] 根据答案,我写了以下 lambda@edge 来解决我的问题。

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    const cookieName = '__a_x_id';

    /*
     * Lambda at the Edge headers are array objects.
     *
     * Client may send multiple Cookie headers, i.e.:
     * > GET /viewerRes/test HTTP/1.1
     * > User-Agent: curl/7.18.1 (x86_64-unknown-linux-gnu) libcurl/7.18.1 OpenSSL/1.0.1u zlib/1.2.3
     * > Cookie: First=1; Second=2
     * > Cookie: ClientCode=abc
     * > Host: example.com
     *
     * You can access the first Cookie header at headers["cookie"][0].value
     * and the second at headers["cookie"][1].value.
     *
     * Header values are not parsed. In the example above,
     * headers["cookie"][0].value is equal to "First=1; Second=2"
     */
    console.log(headers.cookie);
    // Delete the cookie if found
    if (headers.cookie) {
        for (let i = 0; i < headers.cookie.length; i++) {
            console.log(headers.cookie[i].value);
            if (headers.cookie[i].value.indexOf(cookieName) >= 0) {
                console.log('Adblocker cookie found and delete: '+headers.cookie[i].value);
                headers.cookie[i].value = "0";
                break;
            }
        }
        request.headers = headers;
    }

    callback(null, request);
};

为此,您需要编写一个 Lambda@Edge 函数来有条件地过滤 CloudFront 中的 cookie。

检查 this 示例以深入了解所需的操作。另请注意,您需要使用 Lambda@Edge 更改原始请求事件中的请求 header。

快速免责声明:当 cookie 被转发到源服务器时,CloudFront 不仅缓存响应 URI 和 headers(以及查询字符串,如果配置为这样做的话) ),而且还针对浏览器提供的 cookie 值(或缺失 cookie)的唯一组合——因此只有当它们包含(或缺失)完全相同的 cookie 和值组合时,才能从缓存中提供响应。这对您的缓存命中率不利,但当然,这也是 CloudFront 方面完全正确的设计——如果提供了不同的 cookie,CloudFront 别无选择,只能假设该 cookie 可能会修改返回的响应从源头开始,所以cookies必须成为缓存键的组成部分。

如果一定要转发cookies,最好转发特定的cookies。

但是,CloudFront 有许多与缓存无关的应用程序,因此像这样的解决方案可能有有效的用例。


您的解决方案只会通过简单而乐观的测试。它无法正确处理许多边缘情况。用于 cookie 操作的示例脚本只是一个简单的说明,并包含一些免责声明:

* Header values are not parsed.

第一个问题是浏览器可以在一个 Cookie: header 中自由组合多个 cookie,而您对 headers.cookie[i].value.indexOf(cookieName) 的测试不仅会匹配 header使用您想要的 cookie,它将 headers 与该 cookie 加上其他 ... 并删除该特定 header 条目中的所有 cookie。

如果在查看器请求触发器中使用,使用此解决方案删除过多 cookie 的风险很大。在 Origin Request 触发器中,它甚至更高,因为 cookie 已经被匹配缓存行为的 cookie 转发配置剥离和 re-canonicalized,并且 CloudFront 确实 组合多个 cookie在单个 header 行上,至少在某些情况下。

第二个问题与第一个有关:indexOf() 的简单字符串匹配将匹配 cookie 值以及 cookie 名称,因此可能会在 cookie 上得到错误匹配 value -- 您不想检查的值。

第三个问题是您没有真正生成有效的替换值。 CloudFront 似乎暂时接受了这一点,但由于它在技术上是无效的,因此将来可能 "fixed"。

我写了一个 Lambda@Edge 脚本,我相信它可以完全处理 cookie 语义,并且将只删除并准确地删除您要删除的 cookie,并保持数据结构干净。因为我发现这是一个有趣的用例,所以我编写它以便它可以匹配任意数量的 cookie——而不仅仅是一个 cookie——在 exact-string、case-sensitive 匹配上只有 cookie 名称。

cookie 配置在靠近顶部的数组中。

在您的情况下,使用名为 __a_x_id 的 cookie 将如下所示:

const discard = [ '__a_x_id' ]; 

将多个 cookie 名称添加到数组将阻止所有这些名称。

这使用 Node.js 6.10 并与查看器请求触发器或源请求触发器一起使用。如果您要进行任何缓存,您可能希望将其用作 Origin Request 触发器,因为这意味着它触发的频率较低。

我也很高兴地报告,尽管看起来有点复杂并且进行了相当多的字符串拆分并具有多个嵌套循环,但此代码在热容器中的 Lambda 执行时间始终小于 1毫秒,无论是否匹配和删除任何 cookie。

'use strict';

// source: https://whosebug.com/a/45970883/1695906

// iterate through all Cookie: headers in a request trigger,
// removing any cookies on the "discard" list, while preserving
// the integrity of any other cookies, including those appearing on the same
// header line, and confirm the resulting "cookie" array to CloudFront 
// requirements by removing any now-empty elements, or the entire array
// if no cookies remain

// configure with one or more cookies to be removed from all requests;
// cookie names are case-sensitive

const discard = [ 'grover', 'big_bird' ]; // friends of cookie monster

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    // does the request have any cookies? skip to the end, if not
    if(headers.cookie)
    {
        const cookies = headers.cookie;

        // iterate each Cookie: header, from last to first;
        // last-to-first makes it simple to splice-out an array element
        // because we then need not keep track of the reduced array length

        for (var n = cookies.length; n--;)
        {
            // there may be multiple cookies per header line; examine them all

            const cval = cookies[n].value.split(/;\ /);
            const vlen = cval.length; // how many we started with

            // check individual cookies on this line, backwards
            for (var m = vlen; m--;)
            {
                // cookie name is to the left of "="
                const cookie_kv = cval[m].split('=')[0];
                // run though each member of "discard" array,
                // removing the cookie if it's a match, 
                // again last to first but for no particular reason, here
                for(var di = discard.length; di--;)
                {
                    if(cookie_kv == discard[di])
                    {
                        cval.splice(m,1); // cookie removed!
                        break; // no need to check any other matches, already gone
                    }
                }
            } // for m

            // if our array of cookies for this header line has now changed in size,
            // we must have deleted some or all of it, so we need to reassemble
            // what remains, or eliminate the entire line

            if(cval.length != vlen)
            {
                if(cval.length === 0) // did we remove everything?
                {
                    // yes? we can eliminate this entire line
                    cookies.splice(n,1); 
                }
                else
                {
                    // no? reassemble the remaining cookies
                    headers.cookie[n].value = cval.join('; '); 
                }
            }
        } // for n

        // if the only cookies present in the request were cookies we removed,
        // we now have a completely empty array in headers.cookie, which
        // CloudFront should consider invalid; clean it up
        if(cookies.length === 0) 
        {
            delete headers.cookie;
        }
    }

    // return control to CloudFront, possibly with our modified request
    return callback(null, request);

};