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

使用Lambda+CloudFront保护S3网站时,能否在Node.js Lambda中获取URL片段的Access Token?

问题核心解答:Lambda无法直接获取URL片段中的Access Token

首先直接给结论:不行,不管是CloudFront关联的Lambda@Edge还是普通Lambda函数,都没办法直接拿到URL片段(#后面的内容)。

原因很简单:HTTP协议明确规定,浏览器发起请求时只会把#之前的URL部分发送给服务器,#及后面的片段完全是客户端浏览器本地处理的内容,不会出现在HTTP请求头或请求参数里。所以当用户从Cognito登录页跳转回你的网站时,Lambda根本接收不到这部分令牌数据。

不过别担心,有两种靠谱的解决方案可以帮你实现需求:


方案1:客户端脚本提取令牌,再传给Lambda验证

这种方案适合你想继续使用Implicit Flow(Cognito直接返回令牌到URL片段)的场景:

  1. 配置Cognito的回调URL指向一个专门的处理页面,比如your-site.com/callback.html
  2. 在callback.html里写一段JavaScript,提取URL片段中的令牌,再发送给Lambda做验证:
<!DOCTYPE html>
<html>
<head>
    <title>Processing Login</title>
</head>
<body>
    <script>
        // 提取URL片段中的参数
        const hashParams = new URLSearchParams(window.location.hash.slice(1));
        const accessToken = hashParams.get('access_token');
        const idToken = hashParams.get('id_token');

        if (!accessToken) {
            // 没有令牌,跳回登录页
            window.location.href = '/login';
            return;
        }

        // 把令牌发送到Lambda验证(替换成你的Lambda API端点)
        fetch('/api/validate-token', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ accessToken })
        })
        .then(res => res.json())
        .then(data => {
            if (data.isValid) {
                // 验证通过,存储令牌(推荐用HttpOnly Cookie,这里示例用localStorage)
                localStorage.setItem('authToken', accessToken);
                // 跳转到网站主页面
                window.location.href = '/';
            } else {
                window.location.href = '/login';
            }
        })
        .catch(err => {
            console.error('Token validation failed:', err);
            window.location.href = '/login';
        });
    </script>
</body>
</html>
  1. 对应的Lambda验证函数(Node.js)示例:
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

// 初始化Cognito JWKS客户端,用于验证令牌签名
const client = jwksClient({
    jwksUri: 'https://cognito-idp.YOUR_REGION.amazonaws.com/YOUR_USER_POOL_ID/.well-known/jwks.json'
});

function getKey(header, callback) {
    client.getSigningKey(header.kid, function(err, key) {
        const signingKey = key.publicKey || key.rsaPublicKey;
        callback(null, signingKey);
    });
}

exports.handler = async (event) => {
    const { accessToken } = JSON.parse(event.body);

    try {
        // 验证Access Token的有效性
        await new Promise((resolve, reject) => {
            jwt.verify(accessToken, getKey, {
                issuer: 'https://cognito-idp.YOUR_REGION.amazonaws.com/YOUR_USER_POOL_ID',
                audience: 'YOUR_COGNITO_CLIENT_ID'
            }, (err, decoded) => {
                if (err) reject(err);
                else resolve(decoded);
            });
        });

        return {
            statusCode: 200,
            body: JSON.stringify({ isValid: true })
        };
    } catch (err) {
        console.error('Token validation error:', err);
        return {
            statusCode: 401,
            body: JSON.stringify({ isValid: false })
        };
    }
};

方案2:改用Authorization Code Flow(更安全推荐)

这种方案是Cognito官方推荐的服务器端验证流程,不会把令牌暴露在URL里:

  1. 在Cognito用户池的应用客户端设置中,把OAuth授权流改成Authorization Code Grant
  2. 调整登录跳转URL,让Cognito返回授权码(code)而不是令牌:
    https://YOUR_COGNITO_DOMAIN/login?client_id=YOUR_CLIENT_ID&response_type=code&scope=openid&redirect_uri=YOUR_CALLBACK_URL
    
  3. 当用户登录后,跳转回你的网站时,URL会是your-site.com?code=xxx&state=xxx,此时Lambda(比如CloudFront的Viewer Request触发器)可以直接从查询参数中拿到code,然后向Cognito换取令牌:
const axios = require('axios');
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
    jwksUri: 'https://cognito-idp.YOUR_REGION.amazonaws.com/YOUR_USER_POOL_ID/.well-known/jwks.json'
});

function getKey(header, callback) {
    client.getSigningKey(header.kid, function(err, key) {
        const signingKey = key.publicKey || key.rsaPublicKey;
        callback(null, signingKey);
    });
}

exports.handler = async (event) => {
    const request = event.request;
    const queryParams = request.querystring;

    // 检查是否有授权码
    if (queryParams && queryParams.code) {
        const code = queryParams.code.value;
        const tokenEndpoint = 'https://YOUR_COGNITO_DOMAIN/oauth2/token';
        const clientId = 'YOUR_CLIENT_ID';
        const clientSecret = 'YOUR_CLIENT_SECRET'; // 如果应用客户端设置了密钥才需要
        const redirectUri = 'YOUR_CALLBACK_URL';

        try {
            // 向Cognito换取Access Token和ID Token
            const tokenRes = await axios.post(tokenEndpoint, new URLSearchParams({
                grant_type: 'authorization_code',
                client_id: clientId,
                client_secret: clientSecret,
                code: code,
                redirect_uri: redirectUri
            }), {
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
            });

            const { access_token, id_token } = tokenRes.data;

            // 验证ID Token(确保用户身份合法)
            await new Promise((resolve, reject) => {
                jwt.verify(id_token, getKey, {
                    issuer: 'https://cognito-idp.YOUR_REGION.amazonaws.com/YOUR_USER_POOL_ID',
                    audience: clientId
                }, (err, decoded) => {
                    if (err) reject(err);
                    else resolve(decoded);
                });
            });

            // 验证通过,设置HttpOnly Cookie存储令牌,并重定向到主页面清除查询参数
            return {
                statusCode: 302,
                headers: {
                    'location': { value: '/' },
                    'set-cookie': { 
                        value: `accessToken=${access_token}; HttpOnly; Secure; Path=/; SameSite=Strict` 
                    }
                }
            };
        } catch (err) {
            console.error('Token exchange failed:', err);
            return {
                statusCode: 302,
                headers: { 'location': { value: '/login' } }
            };
        }
    }

    // 检查是否有有效的Cookie令牌
    const cookies = request.headers.cookie;
    if (cookies) {
        const accessToken = cookies.split(';').find(c => c.trim().startsWith('accessToken='))?.split('=')[1];
        if (accessToken) {
            // 这里可以添加令牌验证逻辑,验证通过则允许访问
            return request;
        }
    }

    // 未授权,跳转到Cognito登录页
    const loginUrl = 'https://YOUR_COGNITO_DOMAIN/login?client_id=YOUR_CLIENT_ID&response_type=code&scope=openid&redirect_uri=YOUR_CALLBACK_URL';
    return {
        statusCode: 302,
        headers: { 'location': { value: loginUrl } }
    };
};

注意事项

  • 安全性优先:Authorization Code Flow比Implicit Flow更安全,因为令牌不会暴露在URL或客户端存储中(用HttpOnly Cookie可以避免XSS攻击)
  • 令牌验证:无论哪种方案,都必须在Lambda中验证令牌的签名、过期时间、发行方(issuer)和受众(audience),不能仅依赖客户端传递的令牌
  • CloudFront配置:如果用Lambda@Edge,要确保触发器类型是Viewer Request,并且正确配置缓存行为,避免未授权的请求被缓存

内容的提问来源于stack exchange,提问作者syim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:09:08