

# CloudFront Functions 中的 CWT 支持
<a name="cwt-support-cloudfront-functions"></a>

此部分详细介绍 CloudFront Functions 中的 CBOR Web 令牌（CWT）支持功能，可使用该功能在 CloudFront 边缘站点实施基于令牌的安全身份验证和授权。此支持功能以模块形式提供，可通过 CloudFront 函数访问。

要使用此模块，请使用 JavaScript 运行时 2.0 创建 CloudFront 函数，并在函数代码的第一行包含以下语句：

```
import cf from 'cloudfront';
```

可通过以下方式访问与此模块关联的方法（其中，\$1 是表示模块中存在的不同函数的通配符）：

```
cf.cwt.*
```

有关更多信息，请参阅[适用于 CloudFront Functions 的 JavaScript 运行时系统 2.0 特征](functions-javascript-runtime-20.md)。

目前，此模块仅支持采用 HS256（HMAC-SHA256）算法的 MAC0 结构，最大令牌大小的限制为 1 KB。

## 令牌结构
<a name="token-structure"></a>

此部分介绍 CWT 模块所需的令牌结构。该模块要求令牌必须带有正确的标签且可识别（例如，COSE MAC0）。此外，在令牌结构方面，该模块遵循 [CBOR 对象签名与加密（COSE）[RFC 8152]](https://datatracker.ietf.org/doc/html/rfc8152) 设定的标准。

```
( // CWT Tag (Tag value: 61) --- optional    
    ( // COSE MAC0 Structure Tag (Tag value: 17) --- required        
        [            
            protectedHeaders,            
            unprotectedHeaders,            
            payload,            
            tag,        
        ]    
    )
)
```

**Example ：使用 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() 方法
<a name="validatetoken-method"></a>

该函数使用指定密钥对 CWT 令牌进行解码和验证。如果验证成功，则将返回已解码的 CWT 令牌。否则，将引发错误。请注意，该函数不执行任何声明集验证操作。

### 请求
<a name="validatetoken-request"></a>

```
cf.cwt.validateToken(token, handlerContext{key})
```参数

**token（必需）**  
用于验证的编码令牌。这必须是 JavaScript 缓冲区。

**handlerContext（必需）**  
一个 JavaScript 对象，用于存储 validateToken 调用的上下文。目前，仅支持 key 属性。

**key（必需）**  
用于消息摘要计算的私密密钥。可以字符串或 JavaScript 缓冲区的形式提供。

### 响应
<a name="validatetoken-response"></a>

当 `validateToken()` 方法返回成功验证的令牌时，来自函数的响应将为采用以下格式的 `CWTObject`。解码后，所有声明密钥都以字符串形式表示。

```
CWTObject {    
    protectedHeaders,    
    unprotectedHeaders,    
    payload
}
```

### 示例：使用作为令牌一部分发送的 kid 验证令牌
<a name="validatetoken-example"></a>

此示例演示了 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() 方法
<a name="generatetoken-method"></a>

此函数使用提供的有效载荷和上下文设置来生成新的 CWT 令牌。

### 请求
<a name="generatetoken-request"></a>

```
cf.cwt.generateToken(generatorContext, payload)
```参数

**generatorContext（必需）**  
这是一个 JavaScript 对象，用作生成令牌的上下文且包含以下键值对：    
**cwtTag（可选）**  
此值是一个布尔值，如果为 `true`，则指定应添加 `cwtTag`。  
**coseTag（必需）**  
指定 COSE 标签类型。目前仅支持 `MAC0`。  
**key（必需）**  
用于计算消息摘要的私密密钥。此值可以是字符串或 JavaScript `Buffer`。

**payload（必需）**  
用于编码的令牌有效载荷。有效载荷必须采用 `CWTObject` 格式。

### 响应
<a name="generatetoken-response"></a>

返回包含已编码的令牌的 JavaScript 缓冲区。

**Example ：生成 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'
        };
    }
}
```

**Example ：根据某种逻辑刷新令牌**  

```
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;
}
```