CloudFront Functions 中的 CWT 支持
此部分详细介绍 CloudFront Functions 中的 CBOR Web 令牌(CWT)支持功能,可使用该功能在 CloudFront 边缘站点实施基于令牌的安全身份验证和授权。此支持功能以模块形式提供,可通过 CloudFront 函数访问。
要使用此模块,请使用 JavaScript 运行时 2.0 创建 CloudFront 函数,并在函数代码的第一行包含以下语句:
import cf from 'cloudfront';
可通过以下方式访问与此模块关联的方法(其中,* 是表示模块中存在的不同函数的通配符):
cf.cwt.*
有关更多信息,请参阅适用于 CloudFront Functions 的 JavaScript 运行时系统 2.0 特征。
目前,此模块仅支持采用 HS256(HMAC-SHA256)算法的 MAC0 结构,最大令牌大小的限制为 1 KB。
令牌结构
此部分介绍 CWT 模块所需的令牌结构。该模块要求令牌必须带有正确的标签且可识别(例如,COSE MAC0)。此外,在令牌结构方面,该模块遵循 CBOR 对象签名与加密(COSE)[RFC 8152]
( // CWT Tag (Tag value: 61) --- optional ( // COSE MAC0 Structure Tag (Tag value: 17) --- required [ protectedHeaders, unprotectedHeaders, payload, tag, ] ) )
例 :使用 COSE MAC0 结构的 CWT
61( // CWT tag 17( // COSE_MAC0 tag [ { // Protected Headers 1: 4 // algorithm : HMAC-256-64 }, { // Unprotected Headers 4: h'53796d6d6574726963323536' // kid : Symmetric key id }, { // Payload 1: "https://iss.example.com", // iss 2: "exampleUser", // sub 3: "https://aud.example.com", // aud 4: 1444064944, // exp 5: 1443944944, // nbf 6: 1443944944, // iat }, h'093101ef6d789200' // tag ] ) )
注意
生成令牌时,CWT 标签是可选的。不过,COSE 结构标签是必需的。
validateToken() 方法
该函数使用指定密钥对 CWT 令牌进行解码和验证。如果验证成功,则将返回已解码的 CWT 令牌。否则,将引发错误。请注意,该函数不执行任何声明集验证操作。
请求
cf.cwt.validateToken(token, handlerContext{key})
参数
- token(必需)
-
用于验证的编码令牌。这必须是 JavaScript 缓冲区。
- handlerContext(必需)
-
一个 JavaScript 对象,用于存储 validateToken 调用的上下文。目前,仅支持 key 属性。
- key(必需)
-
用于消息摘要计算的私密密钥。可以字符串或 JavaScript 缓冲区的形式提供。
响应
当 validateToken() 方法返回成功验证的令牌时,来自函数的响应将为采用以下格式的 CWTObject。解码后,所有声明密钥都以字符串形式表示。
CWTObject { protectedHeaders, unprotectedHeaders, payload }
示例:使用作为令牌一部分发送的 kid 验证令牌
此示例演示了 CWT 令牌验证过程,其中会从标头中提取 kid。之后,将 kid 传入 CloudFront Functions KeyValueStore 中,以提取用于验证令牌的私密密钥。
import cf from 'cloudfront' const CwtClaims = { iss: 1, aud: 3, exp: 4 } async function handler(event) { try { let request = event.request; let encodedToken = request.headers['x-cwt-token'].value; let kid = request.headers['x-cwt-kid'].value; // Retrieve the secret key from the kvs let secretKey = await cf.kvs().get(kid); // Now you can use the secretKey to decode & validate the token. let tokenBuffer = Buffer.from(encodedToken, 'base64url'); let handlerContext = { key: secretKey, } try { let cwtObj = cf.cwt.validateToken(tokenBuffer, handlerContext); // Check if token is expired const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds if (cwtObj[CwtClaims.exp] && cwtObj[CwtClaims.exp] < currentTime) { return { statusCode: 401, statusDescription: 'Token expired' }; } } catch (error) { return { statusCode: 401, statusDescription: 'Invalid token' }; } } catch (error) { return { statusCode: 402, statusDescription: 'Token processing failed' }; } return request; }
generateToken() 方法
此函数使用提供的有效载荷和上下文设置来生成新的 CWT 令牌。
请求
cf.cwt.generateToken(generatorContext, payload)
参数
- generatorContext(必需)
-
这是一个 JavaScript 对象,用作生成令牌的上下文且包含以下键值对:
- cwtTag(可选)
-
此值是一个布尔值,如果为
true,则指定应添加cwtTag。 - coseTag(必需)
-
指定 COSE 标签类型。目前仅支持
MAC0。 - key(必需)
-
用于计算消息摘要的私密密钥。此值可以是字符串或 JavaScript
Buffer。
- payload(必需)
-
用于编码的令牌有效载荷。有效载荷必须采用
CWTObject格式。
响应
返回包含已编码的令牌的 JavaScript 缓冲区。
例 :生成 CWT 令牌
import cf from 'cloudfront'; const CwtClaims = { iss: 1, sub: 2, exp: 4 }; const CatClaims = { catu: 401, catnip: 402, catm: 403, catr: 404 }; const Catu = { host: 1, path: 2, ext: 3 }; const CatuMatchTypes = { prefix_match: 1, suffix_match: 2, exact_match: 3 }; const Catr = { renewal_method: 1, next_renewal_time: 2, max_uses: 3 }; async function handler(event) { try { const response = { statusCode: 200, statusDescription: 'OK', headers: {} }; const commonAccessToken = { protected: { 1: "5", }, unprotected: {}, payload: { [CwtClaims.iss]: "cloudfront-documentation", [CwtClaims.sub]: "cwt-support-on-cloudfront-functions", [CwtClaims.exp]: 1740000000, [CatClaims.catu]: { [Catu.host]: { [CatuMatchTypes.suffix_match]: ".cloudfront.net" }, [Catu.path]: { [CatuMatchTypes.prefix_match]: "/media/live-stream/cf-4k/" }, [Catu.ext]: { [CatuMatchTypes.exact_match]: [ ".m3u8", ".ts", ".mpd" ] } }, [CatClaims.catnip]: [ "[IP_ADDRESS]", "[IP_ADDRESS]" ], [CatClaims.catm]: [ "GET", "HEAD" ], [CatClaims.catr]: { [Catr.renewal_method]: "header_renewal", [Catr.next_renewal_time]: 1750000000, [Catr.max_uses]: 5 } } }; if (!request.headers['x-cwt-kid']) { throw new Error('Missing x-cwt-kid header'); } const kid = request.headers['x-cwt-kid'].value; const secretKey = await cf.kvs().get(kid); if (!secretKey) { throw new Error('Secret key not found for provided kid'); } try { const genContext = { cwtTag: true, coseTag: "MAC0", key: secretKey }; const tokenBuffer = cf.cwt.generateToken(commonAccessToken, genContext); response.headers['x-generated-cwt-token'] = { value: tokenBuffer.toString('base64url') }; return response; } catch (tokenError) { return { statusCode: 401, statusDescription: 'Could not generate the token' }; } } catch (error) { return { statusCode: 402, statusDescription: 'Token processing failed' }; } }
例 :根据某种逻辑刷新令牌
import cf from 'cloudfront' const CwtClaims = { iss: 1, aud: 3, exp: 4 } async function handler(event) { try { let request = event.request; let encodedToken = request.headers['x-cwt-token'].value; let kid = request.headers['x-cwt-kid'].value; let secretKey = await cf.kvs().get(kid); // Retrieve the secret key from the kvs // Now you can use the secretKey to decode & validate the token. let tokenBuffer = Buffer.from(encodedToken, 'base64url'); let handlerContext = { key: secretKey, } try { let cwtJSON = cf.cwt.validateToken(tokenBuffer, handlerContext); // Check if token is expired const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds if (cwtJSON[CwtClaims.exp] && cwtJSON[CwtClaims.exp] < currentTime) { // We can regnerate the token and add 8 hours to the expiry time cwtJSON[CwtClaims.exp] = Math.floor(Date.now() / 1000) + (8 * 60 * 60); let genContext = { coseTag: "MAC0", key: secretKey } let newTokenBuffer = cf.cwt.generateToken(cwtJSON, genContext); request.headers['x-cwt-regenerated-token'] = newTokenBuffer.toString('base64url'); } } catch (error) { return { statusCode: 401, statusDescription: 'Invalid token' }; } } catch (error) { return { statusCode: 402, statusDescription: 'Token processing failed' }; } return request; }