我在Symfony Web应用程序前面使用CloudFront作为缓存。为了根据用户的角色(管理员,客户......)获取缓存,我在Lambda @ Edge Viewer请求触发器中生成基于用户角色的哈希。我将该哈希值作为请求标头传递给我的源X-User-Context-Hash
。
我现在的问题是我需要将PHPSESSID
cookie传递给我的源,以获得正确的缓存响应,但我不想将缓存基于PHPSESSID
的值。我只需要我的缓存响应基于X-User-Context-Hash
的值而不是我的会话cookie。
下面的图片应该详细解释我的问题
有没有可能实现这个目标?
感谢任何帮助。
这是我的Lambda @ Edge Viewer请求触发器:
'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();
}
;
答案 0 :(得分:5)
以下是我最终解决问题的方法:
我将行为配置为不将任何Cookie转发到原点,而只根据标题Host
和X-User-Context-Hash
进行缓存(参见屏幕截图)。
PHPSESSID
和REMEMBERME
的基于用户的Cookie,并通过X-Session-Cookies
标题传递这些值。Host
和X-User-Context-Hash
标头匹配,则Cloud-Front会返回缓存的项目并停在此处。X-Session-Cookies
可用。因此,我从X-Session-Cookies
标头中取值,并将request.headers.cookie
的值设置为该值。此步骤可确保在缓存页面之前将PHPSESSID
和REMEMBERME
Cookie都传递到源。查看者请求触发器:
'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);
};
答案 1 :(得分:1)
根本问题:
如果您将CloudFront配置为将Cookie转发到您的来源,CloudFront会根据Cookie值进行缓存。即使您的来源忽略了请求中的cookie值,也是如此......
这是设计的。您转发的Cookie始终是缓存密钥的一部分。
没有干净/简单/明显的解决方法。
您可以将会话cookie添加到查看器请求触发器中的查询字符串,并将该参数配置为转发但不用于缓存,然后您的源需要在那里找到它并将其解释为cookie。与cookie不同,查询字符串参数可以配置为转发,但不能用于缓存。
您可以使用虚拟/占位符值(每个用户类一个)替换实际请求中的cookie,以便将其转发到源并用于缓存,然后使用查看器响应触发器来阻止任何来自原点(或缓存)的Set-Cookie
响应,将该魔法cookie暴露给任何观众。
但实际上,听起来好像你可能试图在一个地方解决一个真正需要在另一个地方解决的问题。您的应用程序的设计存在限制,对某些资源不具有缓存友好性。这些资源需要设计为以缓存友好的方式进行交互,当访问资源需要对用户,角色,组,权限等进行身份验证时,这当然是一个非常棘手的主张。
答案 2 :(得分:0)
类似的事情我确实能够将cookie转发到为粘性会话配置的ALB,而无需使用cookie缓存的cloudfront。这样做的原因是因为Cloudfront在匹配请求和缓存的响应时将使用cookie及其值,但是由于ALB在每个响应上创建一个新的会话cookie,因此该请求永远不会与缓存匹配,因为查看器始终设置了新的cookie值。
我也将cookie移到查看器请求中的自定义标头上,然后将其从标头中拉出并放回原始请求中的cookie中。我对查看器请求和原始请求使用了相同的lambda函数,只是检查了config属性以确定它是哪个触发器。我更喜欢这种模式,以便在读取/写入代码时可以遵循从查看器请求到原始请求的逻辑,因为来自查看器请求的回调响应成为原始请求的事件请求,并且我可以在单个函数上运行测试。逻辑基于以下三个流程:
使用这三个用例,该功能可以正常工作。
功能如下:
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);
};
答案 3 :(得分:0)
AWS最近introduced缓存和原始请求策略,允许进行更多自定义。
现在,您可以通过设置适当的缓存策略并将原始请求策略设置为仅转发必要的数据,来基于Cookie /查询字符串参数的“所有”列表设置缓存行为: