7

我在 Symfony Web 应用程序前面使用 CloudFront 作为缓存。为了根据用户角色(管理员、客户...)获取缓存,我在 Lambda@Edge 查看器请求触发器中生成基于用户角色的哈希。我将该哈希作为请求标头传递给我的来源,作为X-User-Context-Hash.

我现在的问题是我需要将PHPSESSIDcookie 传递到我的源以获得正确的缓存响应,但我不想将缓存基于PHPSESSID. 我只需要基于我的会话 cookie 的值X-User-Context-Hash而不是基于我的会话 cookie 的缓存响应。

下面的图片应该详细解释我的问题

有没有可能做到这一点?

将不胜感激任何帮助。

在此处输入图像描述

这是我的 Lambda@Edge 查看器请求触发器:

'use strict';

function parseCookies(headers) {
    const parsedCookie = {};
    if (headers.cookie) {

        console.log(`${headers.cookie[0].value}`);

        headers.cookie[0].value.split(';').forEach((cookie) => {
            if (cookie) {
                const parts = cookie.split('=');
                parsedCookie[parts[0].trim()] = parts[1].trim();
            }
        });
    }
    return parsedCookie;
}

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

    const https = require('https');

    // Read session cookie
    const parsedCookies = parseCookies(headers);
    let cookie = '';
    if (parsedCookies) {
        if(parsedCookies['PHPSESSID']) {
            cookie = `PHPSESSID=${parsedCookies['PHPSESSID']}`;
        }
    }

    console.log(`Cookie: ${cookie}`);

    // Send request to origin host at /_fos_user_context_hash
    // passing the original session cookie
    const options = {
        hostname: `${request.headers.host[0].value}`,
        port: 443,
        path: '/_fos_user_context_hash',
        method: 'HEAD',
        headers: {
            'Cookie': cookie, 
            'Accept': 'application/vnd.fos.user-context-hash',
            'Vary' : 'Cookie'
        }
    };

    const req = https.request(options, (res) => {
      console.log('statusCode:', res.statusCode);
      console.log('headers:', res.headers);

      // Read the X-User-Context-Hash from the hash endpoint
      const headerName = 'X-User-Context-Hash';
      let hash = 'anonymous';

      if (res.headers[headerName.toLowerCase()]) {
        hash = res.headers[headerName.toLowerCase()];
      }

      // Append X-User-Context-Hash before passing request on to CF
      request.headers[headerName.toLowerCase()] = [{ key: headerName, value: hash }];  

      callback(null, request);

    }).on('error', (e) => {
      console.error(e);
      // Forward request anyway
      callback(null, request);
    });

    req.end();
}


;
4

4 回答 4

7

这是我最终解决问题的方法:

CloudFront 行为

我将行为配置为不将任何 cookie 转发到源,而仅基于标头缓存HostX-User-Context-Hash(请参见屏幕截图)。

屏幕截图 CloudFront 行为

下图解释了我的 lambda@edge 过程: lambda@edge 进程

  1. 在“查看器请求”触发器中,我读取了名为的基于用户的 cookie PHPSESSIDREMEMBERME并通过X-Session-Cookies标题传递这些值。
  2. 如果我的请求 url 和给定的标头匹配HostX-User-Context-HashCloud-Front 将返回缓存的项目并在此处停止。
  3. 如果不匹配,则触发“Origin Request”触发器。当该事件触发时,自定义标头X-Session-Cookies可用。所以我从X-Session-Cookies标题中获取值并将值设置request.headers.cookie为该值。此步骤可确保在页面缓存之前将PHPSESSIDREMEMBERMEcookie 都传递到源。

我的 Lambda@Edge 函数:

查看器请求触发器:

'use strict';

function parseCookies(headers) {
    const parsedCookie = {};
    if (headers.cookie) {

        console.log(`${headers.cookie[0].value}`);

        headers.cookie[0].value.split(';').forEach((cookie) => {
            if (cookie) {
                const parts = cookie.split('=');
                parsedCookie[parts[0].trim()] = parts[1].trim();
            }
        });
    }
    return parsedCookie;
}

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

    const https = require('https');

    let sessionId = '';

    // Read session cookie
    const parsedCookies = parseCookies(headers);
    let cookie = '';
    if (parsedCookies) {
        if(parsedCookies['PHPSESSID']) {
            cookie = `PHPSESSID=${parsedCookies['PHPSESSID']}`;
        }
        if(parsedCookies['REMEMBERME']) {
            if (cookie.length > 0) {
                cookie += ';';
            }
            cookie += `REMEMBERME=${parsedCookies['REMEMBERME']}`;
        }
    }

    console.log(`Cookie: ${cookie}`);

    // Send request to origin host at /_fos_user_context_hash
    // passing the original session cookie
    const options = {
        hostname: `${request.headers.host[0].value}`,
        port: 443,
        path: '/_fos_user_context_hash',
        method: 'HEAD',
        headers: {
            'Cookie': cookie, 
            'Accept': 'application/vnd.fos.user-context-hash',
            'Vary' : 'Cookie'
        }
    };

    const req = https.request(options, (res) => {
      console.log('statusCode:', res.statusCode);
      console.log('headers:', res.headers);

      // Read the X-User-Context-Hash from the hash endpoint
      const headerName = 'X-User-Context-Hash';
      let hash = 'anonymous';

      if (res.headers[headerName.toLowerCase()]) {
        hash = res.headers[headerName.toLowerCase()];
      }

      // Append X-User-Context-Hash before passing request on to CF
      request.headers[headerName.toLowerCase()] = [{ key: headerName, value: hash }];

      const sessionHeaderName = 'X-Session-Cookies';
      request.headers[sessionHeaderName.toLowerCase()] = [{ key: sessionHeaderName, value: cookie }];  

      callback(null, request);

    }).on('error', (e) => {
      console.error(e);
      // Forward request anyway
      callback(null, request);
    });

    req.end();
}


;

源请求触发器:

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

    const sessionHeaderName = 'X-Session-Cookies';

    let cookie = '';
    if (request.headers[sessionHeaderName.toLowerCase()]) {
        console.log(request.headers[sessionHeaderName.toLowerCase()]);
        cookie = request.headers[sessionHeaderName.toLowerCase()][0].value;
    }

    request.headers.cookie = [{ key : 'Cookie', value : cookie }];

    callback(null, request);
};
于 2017-12-01T14:25:09.157 回答
5

AWS 最近引入了缓存和源请求策略,允许更多自定义。

您现在可以通过设置适当的缓存策略并设置源请求策略以仅转发必要的数据,根据 cookie/查询字符串参数的“所有除外”列表设置缓存行为:

在此处输入图像描述

于 2020-09-18T07:50:29.597 回答
2

根本问题:

如果您将 CloudFront 配置为将 cookie 转发到您的源,CloudFront 会根据 cookie 值进行缓存。即使您的来源忽略请求中的 cookie 值也是如此...

http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Cookies.html

这是设计使然。您转发的 Cookie 始终是缓存键的一部分。

没有干净/简单/明显的解决方法。

您可以将会话 cookie 添加到查看器请求触发器中的查询字符串,并将该参数配置为转发但不用于缓存,然后您的源需要在那里找到它并将其解释为 cookie。与 cookie 不同,查询字符串参数可以配置为转发,但不能缓存。

您可以将实际请求中的 cookie 替换为虚拟/占位符值,每个用户类一个,以便将其转发到源并用于缓存,然后使用查看器响应触发器来阻止Set-Cookie来自源(或缓存)从将魔术 cookie 暴露给任何观众。

但是,实际上,听起来好像您可能正试图在一个地方解决一个真正需要在另一个地方解决的问题。您的应用程序在其设计中存在一个限制,即对某些资源不支持缓存。这些资源需要设计为以缓存友好的方式进行交互,当访问资源需要用户、角色、组、权限等的身份验证标识时,这当然是一个从根本上棘手的提议。

于 2017-11-30T16:47:19.233 回答
1

我做了类似的事情,以便能够将 cookie 转发到为粘性会话配置的 ALB,而无需使用 cookie 进行缓存。这样做的原因是因为 Cloudfront 在匹配请求和缓存的响应时将使用 cookie 及其值,但是因为 ALB 在每个响应上创建一个新的会话 cookie,所以请求永远不会匹配缓存,因为查看器总是有一个新的 cookie 值集。

我也将 cookie 移动到查看器请求中的自定义标头,然后将其从标头中拉出并放回原始请求中的 cookie 中。我对查看器请求和原始请求使用了相同的 lambda 函数,并检查了配置属性以确定它是哪个触发器。我更喜欢这种模式,这样我可以在读取/编写代码时遵循从查看器请求到源请求的逻辑,因为查看器请求的回调响应成为源请求的事件请求,我可以在单个函数上运行我的测试。该逻辑基于三个流程:

  1. 根本没有 AWSALB cookie 或客户标头 - 在这种情况下什么也不做
  2. 请求中有 AWSALB cookie 但没有其他 cookie
  3. 这是 AWSALB cookie 以及其他 cookie

使用这三个用例,该功能能够正常工作。

这是功能:

exports.handler = (event, context, callback) => {
// TODO implement
const util = require('util');

const COOKIE_TO_FORWARD = 'AWSALB';

let hasTheHeader = (request, headerKey) => {
    if (request.headers[headerKey]) {
        return true;
    } 
    else return false;
}

//Returns the cookie key name from the value of the cookie header in the request
//let getCookieKey = cookieString => cookieString.slice(0,cookieString.indexOf("="));


const request = event.Records[0].cf.request
if(event.Records[0].cf.config.eventType == 'viewer-request') {
    console.log('Viewer Request');
    console.log(`viewer request – ${util.inspect(event, {showHidden: false, depth: null})}`);
    hasTheHeader(event.Records[0].cf.request, 'cookie') ? console.log(`This request has cookies`) : console.log(`This request does NOT have cookies`);

    // First check – If no cookies in Viewer Request, do nothing
    if (!hasTheHeader(request, 'cookie')) {
        console.log('viewer request first check evaluated - no cookies');

        //pass request onto cloudfront cacheing layer or origin request
        callback(null, request);

        return;
    }


   // else there is a cookie header so get the list of cookies and put them in an array
   let cookieList = request.headers.cookie[0].value.split('; ');
   console.log(cookieList);

    // Second check - If only the COOKIE_TO_FORWARD cookie exists and no other cookies, move it to a custom header and delete the cookie header

    if ( (cookieList.length == 1) && (cookieList[0].startsWith(COOKIE_TO_FORWARD)) ) {
        console.log('viewer request second check evaluated - only the COOKIE_TO_FORWARD cookie exists, no other cookies')

        //move awsalb to custom header - format is important
        request.headers.awsalbkey = [{'key': 'awsAlbKey', 'value': cookieList[0]}];

        //remove cookie header
        delete request.headers.cookie;

        console.log(util.inspect(request, {showHidden: false, depth: null}));
        //pass request onto cloudfront cacheing layer or origin request
        callback(null, request);

        return;
    }

    // Third check - If there are multiple cookies including the COOKIE_TO_FORWARD cookie, move only the COOKIE_TO_FORWARD cookie to a custom header and delete the cookie COOKIE_TO_FORWARD cookie

    // get awsAlb cookie
    const indexOfAwsALbCookie = cookieList.findIndex(element => element.startsWith('AWSALB='));
    if ( (cookieList.length > 1) && (indexOfAwsALbCookie > -1) ) {
        console.log('viewer request third check evaluated - the COOKIE_TO_FORWARD cookie exists along with other cookies')


        //put awsAlb cookie value to custom header - format is important
        request.headers.awsalbkey = [{'key': 'awsAlbKey', 'value': cookieList[indexOfAwsALbCookie]}];

        //remove awsAlb cookie from list off cookies in request
        cookieList.splice(indexOfAwsALbCookie,1);
        let cookieListString = cookieList.join('; ');
        request.headers.cookie[0].value = cookieListString;

        console.log(util.inspect(request, {showHidden: false, depth: null}));
        //pass request onto cloudfront cacheing layer or origin request
        callback(null, request);

        return;
    }


}
else if(event.Records[0].cf.config.eventType == 'origin-request') {
    console.log('Origin Request');
    console.log(`origin request – ${util.inspect(event, {showHidden: false, depth: null})}`);
    hasTheHeader(request, 'cookie') ? console.log(`This request has cookies`) : console.log(`This request does NOT have cookies`);

    // First check – If no cookies in Viewer Request AND no awsalbkey header, do nothing as this is the first request to the origin
    if (!hasTheHeader(request, 'cookie') && !hasTheHeader(request, 'awsalbkey')) {
        console.log('origin request first check evaluated - no cookies and no awsalbkey header');

        //send request to origin
        callback(null, request);

        return;
    }



    //Second check, if no cookie header AND COOKIE_TO_FORWARD customer header exists, then add the cookie header and cookie and remove the COOKIE_TO_FORWARD custom header
    if (!hasTheHeader(request, 'cookie') && hasTheHeader(request, 'awsalbkey')) {
        console.log('origin request second check evaluated - no cookies and has the awsalbkey header')

        //add the cookie header and the cookie obtained from the custom header
        request.headers.cookie = [];    
        var length = request.headers.cookie.push({'key': 'Cookie', 'value': request.headers.awsalbkey[0].value});

        //remove the custom header
        delete request.headers.awsalbkey;

        console.log(util.inspect(request, {showHidden: false, depth: null}));
        //send request to origin
        callback(null, request);

        return;
    }

    //else cookie list exists
    let cookieListOrigin = request.headers.cookie[0].value.split('; ');
    console.log(cookieListOrigin);

    // Third check - If there are multiple cookies excluding the COOKIE_TO_FORWARD cookie and there's an COOKIE_TO_FORWARD custom header, move the COOKIE_TO_FORWARD custom header to the list of cookies and remove the COOKIE_TO_FORWARD custom header
    let originIndexAwsAlbCookie = cookieListOrigin.findIndex(element =>  element.startsWith(COOKIE_TO_FORWARD));

    if ( (originIndexAwsAlbCookie < 0) && (cookieListOrigin.length > 0) && (request.headers.awsalbkey) ) {
        console.log('origin request third check evaluated - cookies exist without the awsalb cookie and has the awsalbkey header')

        //add the awsalb customer header value to a new cookie in the cookie array

        var length = cookieListOrigin.push(request.headers.awsalbkey[0].value);
        let cookieListOriginString = cookieListOrigin.join('; ');
        request.headers.cookie[0].value = cookieListOriginString;

        //remove the custom header
        delete request.headers.awsalbkey;

        console.log(util.inspect(request, {showHidden: false, depth: null}));
        //send request to origin
        callback(null, request);

        return;
    }


}

callback(null, request);
};
于 2018-08-12T17:56:25.017 回答