You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CloudFront Lambda@Edge Set-Cookie头可见但浏览器未写入Cookie问题排查

Lambda@Edge + CloudFront Cookie不生效及鉴权逻辑修复方案

故障根因

问题由三类问题共同导致:

  • Cookie属性配置缺失:现有Set-Cookie头未设置Path=/属性,默认Cookie生效范围为首次请求的子路径,其他路径发起的请求不会携带该Cookie;若使用SameSite=None配置,除了必须搭配Secure属性外,跨域场景下若CORS配置未开启凭证允许,浏览器会直接拒绝写入Cookie。
  • 代码逻辑缺陷:
    1. Viewer Request阶段仅从querystring提取jwt,未实现Cookie读取逻辑,querystring无jwt时直接返回401;
    2. querystring参数解析用split("jwt=")的写法存在兼容问题,当jwt不是querystring最后一个参数时,提取到的token会携带后续参数内容,直接导致验签失败;
    3. base64url解码逻辑未处理JWT使用的非标准base64字符(-/_)及补位符,部分token会解码失败;
    4. Viewer Response阶段直接覆盖set-cookie头数组,会清空源站返回的其他Cookie,且未判断token是否存在,querystring无jwt时会写入token=undefined的无效Cookie;响应阶段逻辑异常时直接返回401,会中断正常响应。
  • CloudFront配置遗漏:
    1. 若缓存策略未将Cookie头、jwt query参数纳入转发白名单,CloudFront会截断对应内容,Lambda@Edge读不到Cookie、客户端收不到Set-Cookie头;
    2. 若分发允许HTTP访问,Secure标记的Cookie不会被浏览器存储;
    3. 跨域场景下若响应头未正确配置CORS凭证相关参数,浏览器会拦截Cookie写入。

分步修复方案

1. CloudFront必要配置调整

  • 调整绑定对应分发行为的缓存策略:将Cookie设置为全量转发或至少转发名为token的Cookie;Querystring中将jwt加入转发白名单,避免参数被CloudFront缓存层截断。
  • 调整响应头策略:不要在托管/自定义响应头策略中覆盖Set-Cookie头,确保Lambda@Edge写入的响应头能透传给客户端;跨域场景下需将Access-Control-Allow-Credentials设为true,且Access-Control-Allow-Origin不能配置为通配符*,需填写明确的前端站点域名。
  • 调整查看器协议策略:选择「重定向HTTP到HTTPS」,兼容Secure属性Cookie的存储要求。

2. Viewer Request 函数修复

修复参数解析、Cookie读取、base64解码逻辑,修复后代码如下:

const crypto = require("crypto");

const response401 = {
    statusCode: 401,
    statusDescription: 'Unauthorized',
    headers: {
        'content-type': [{ key: 'Content-Type', value: 'text/plain' }]
    },
    body: 'Unauthorized'
};

// 简易Cookie解析工具
function parseCookies(cookieHeader) {
    const cookies = {};
    if (!cookieHeader) return cookies;
    cookieHeader.split(';').forEach(cookie => {
        const [name, ...rest] = cookie.trim().split('=');
        cookies[name.trim()] = decodeURIComponent(rest.join('='));
    });
    return cookies;
}

exports.handler = async (event, context) => {
    const request = event.Records[0].cf.request;
    const key = "替换为你的实际SECRET KEY";
    let token = null;

    // 优先从querystring取jwt,兼容任意参数位置
    const queryParams = new URLSearchParams(request.querystring);
    if (queryParams.has('jwt')) {
        token = queryParams.get('jwt');
    } else {
        // querystring无jwt时,从Cookie头读取token
        const cookieHeader = request.headers.cookie ? request.headers.cookie[0].value : '';
        const cookies = parseCookies(cookieHeader);
        token = cookies.token || null;
    }
  
    if(!token){
        console.log("Missing authorization token.");
        return response401;
    }
    
    try {
        jwt_decode(token,key)
    } catch (error) {
        console.log("Token validate failed:", error);
        return response401;
    }
    return request;
};

function jwt_decode(token, key, noVerify, algorithm) {
  if (!token) throw new Error("No token supplied");
  var segments = token.split(".");
  if (segments.length !== 3) throw new Error("Not enough or too many segments");

  var headerSeg = segments[0];
  var payloadSeg = segments[1];
  var signatureSeg = segments[2];
  
  var header = JSON.parse(_base64urlDecode(headerSeg));
  var payload = JSON.parse(_base64urlDecode(payloadSeg));

  if (!noVerify) {
    var signingMethod = "sha256";
    var signingType = "hmac";
    var signingInput = [headerSeg, payloadSeg].join(".");

    if (!_verify(signingInput, key, signingMethod, signingType, signatureSeg)) {
      throw new Error("Signature verification failed");
    }

    if (payload.nbf && Date.now() < payload.nbf * 1000) {
      throw new Error("Token not yet active");
    }

    if (payload.exp && Date.now() > payload.exp * 1000) {
      throw new Error("Token expired");
    }
  }
  return payload;
}

function _verify(input, key, method, type, signature) {
  if (type === "hmac") {
    return signature === _sign(input, key, method);
  } else {
    throw new Error("Algorithm type not recognized");
  }
}

function _sign(input, key, method) {
  return crypto.createHmac(method, key).update(input).digest("base64url");
}

// 修复base64url解码逻辑,兼容JWT编码格式
function _base64urlDecode(str) {
  let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
  const pad = base64.length % 4;
  if (pad) {
    if (pad === 1) throw new Error('Invalid base64url string');
    base64 += '='.repeat(4 - pad);
  }
  return Buffer.from(base64, "base64").toString("utf-8");
}

3. Viewer Response 函数修复

修复Cookie属性、Set-Cookie头写入逻辑,异常场景不中断正常响应,修复后代码如下:

exports.handler = async (event, context) => {
    try {
        const response = event.Records[0].cf.response;
        const request = event.Records[0].cf.request;
        const headers = response.headers;
   
        const headerNameSrc = 'X-Amz-Meta-Last-Modified';
        const headerNameDst = 'Last-Modified';

        if (headers[headerNameSrc.toLowerCase()]) {
            headers[headerNameDst.toLowerCase()] = [{
                key: headerNameDst,
                value: headers[headerNameSrc.toLowerCase()][0].value,
            }];
        }
   
        // 仅当querystring携带jwt时才写入Cookie
        const queryParams = new URLSearchParams(request.querystring);
        const token = queryParams.get('jwt');
        if (token) {
            // 补全Path=/确保全站生效;同站访问用SameSite=Lax即可,跨站携带Cookie再改回SameSite=None
            const cookieValue = `token=${token}; Path=/; Secure; HttpOnly; Max-Age=18000; SameSite=Lax`;
            // 追加Set-Cookie头,不覆盖源站返回的其他Cookie
            const existingSetCookie = headers['set-cookie'] || [];
            existingSetCookie.push({
                key: "Set-Cookie",
                value: cookieValue
            });
            headers['set-cookie'] = existingSetCookie;
        }

        return response;
    } catch (error) {
        console.log("Viewer response error:", error);
        // 响应阶段逻辑异常时返回正常源站响应,不中断业务
        return event.Records[0].cf.response;
    }
};

验证注意事项

  • 测试时必须使用HTTPS协议访问CloudFront分发地址,HTTP场景下Secure标记的Cookie不会被浏览器存储。
  • 首次访问带jwt参数的地址后,打开浏览器开发者工具-应用-Cookie面板,检查对应域名下是否写入token项,确认Path、有效期、SameSite属性符合预期。
  • 跨域调用场景下,前端请求需开启凭证携带配置(如axios的withCredentials: true),否则跨域请求不会自动携带Cookie。

内容的提问来源于stack exchange,提问作者T.Demirer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 20:51:32