

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 配置授权和身份验证以保护您的 GraphQL APIs
<a name="security-authz"></a>

AWS AppSync 提供以下授权类型来保护 GraphQL APIs：API 密钥、Lambda、IAM、OpenID Connect 和 Cognito 用户池。每个选项都提供不同的安全方法：

1. **API 密钥授权**：控制未经身份验证的限制 APIs，提供一个简单的安全选项。

1. **Lambda 授权**：启用自定义授权逻辑，详细解释函数输入和输出。

1. **IAM 授权**：利用 AWS签名版本 4 签名流程，允许通过 IAM 策略进行精细的访问控制。

1. **OpenID Connect 授权**：与 OIDC 兼容服务进行集成，用于用户身份验证。

1. **Cognito 用户池**：使用 Cognito 的用户管理特征实施基于群组的访问控制。

## 授权类型
<a name="authorization-types"></a>

您可以通过五种方式授权应用程序与您的 AWS AppSync GraphQL API 进行交互。您可以通过在 AWS AppSync API 或 CLI 调用中指定以下授权类型值之一来指定所使用的授权类型：
+   
** `API_KEY` **  
适用于使用 API 密钥。
+   
** `AWS_LAMBDA` **  
用于使用 AWS Lambda 函数。
+   
** `AWS_IAM` **  
用于使用 AWS Identity and Access Management ([IAM](https://aws.amazon.com/iam/)) 权限。
+   
** `OPENID_CONNECT` **  
适用于使用 OpenID Connect 提供程序。
+   
** `AMAZON_COGNITO_USER_POOLS` **  
适用于使用 Amazon Cognito 用户池。

这些基本授权类型适用于大多数开发人员。对于更高级的使用案例，您可以通过控制台、CLI 和 AWS CloudFormation添加其他授权模式。对于其他授权模式， AWS AppSync 提供一种采用上面列出的值的授权类型（即`API_KEY`、`AWS_LAMBDA`、`AWS_IAM`、`OPENID_CONNECT`、和`AMAZON_COGNITO_USER_POOLS`）。

在将 `API_KEY`、`AWS_LAMBDA` 或 `AWS_IAM` 指定为主要或默认授权类型时，您不能再次将其指定为其他授权模式之一。同样，您不能在其他授权模式中重复使用 `API_KEY`、`AWS_LAMBDA` 或 `AWS_IAM`。您可以使用多个 Amazon Cognito 用户池和 OpenID Connect 提供程序。不过，您不能在默认授权模式和任何其他授权模式之间使用重复的 Amazon Cognito 用户池或 OpenID Connect 提供程序。您可以使用相应的配置正则表达式，为 Amazon Cognito 用户池或 OpenID Connect 提供程序指定不同的客户端。

当您保存对 API 配置的更改时，会 AWS AppSync 开始传播更改。在您的配置更改传播之前， AWS AppSync 将继续提供先前配置中的内容。在您的配置更改传播后， AWS AppSync 立即开始根据新配置提供您的内容。 AWS AppSync 在传播您对某个 API 的更改时，我们无法确定该 API 是根据之前的配置还是新的配置提供您的内容。

## API\$1KEY 授权
<a name="api-key-authorization"></a>

未经身份验证 APIs 需要比经过身份验证更严格的限制。 APIs一种对未经身份验证的 GraphQL 终端节点的限制进行控制的方法是使用 API 密钥。API 密钥是应用程序中的一个硬编码值，当您创建未经身份验证的 GraphQL 端点时由 AWS AppSync 服务生成。您可以通过控制台或 CLI 轮换 API 密钥，或者按照 [AWS AppSync API reference](https://docs.aws.amazon.com/appsync/latest/APIReference/) 轮换 API 密钥。

------
#### [ Console ]

1. 登录 AWS 管理控制台 并打开[AppSync 控制台](https://console.aws.amazon.com/appsync/)。

   1. 在**APIs 控制面板**中，选择你的 GraphQL API。

   1. 在**侧边栏**中，选择**设置**。

1. 在**默认授权模式**下面，选择 **API 密钥**。

1. 在 **API 密钥**表中，选择**添加 API 密钥**。

   将在表中生成一个新的 API 密钥。

   1. 要删除旧 API 密钥，请在表中选择该 API 密钥，然后选择**删除**。

1. 在页面底部选择 **Save (保存)**。

------
#### [ CLI ]

1. 如果您尚未这样做，请配置您对 AWS CLI 的访问权限。有关更多信息，请参阅[配置基础知识](https://docs.aws.amazon.com//cli/latest/userguide/cli-configure-quickstart.html)。

1. 运行 [https://awscli.amazonaws.com/v2/documentation/api/latest/reference/appsync/update-graphql-api.html](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/appsync/update-graphql-api.html) 命令以创建 GraphQL API 对象。

   您需要为该特定命令键入两个参数：

   1. 您的 GraphQL API 的 `api-id`。

   1. 您的 API 的新 `name`。您可以使用相同的 `name`。

   1. `authentication-type`，它是 `API_KEY`。
**注意**  
必须配置其他参数（例如 `Region`），但这些参数通常默认为您的 CLI 配置值。

   示例命令可能如下所示：

   ```
   aws appsync update-graphql-api --api-id abcdefghijklmnopqrstuvwxyz --name TestAPI --authentication-type API_KEY
   ```

   将在 CLI 中返回输出。以下是 JSON 格式的示例：

   ```
   {
       "graphqlApi": {
           "xrayEnabled": false,
           "name": "TestAPI",
           "authenticationType": "API_KEY",
           "tags": {},
           "apiId": "abcdefghijklmnopqrstuvwxyz",
           "uris": {
               "GRAPHQL": "https://s8i3kk3ufhe9034ujnv73r513e.appsync-api.us-west-2.amazonaws.com/graphql",
               "REALTIME": "wss://s8i3kk3ufhe9034ujnv73r513e.appsync-realtime-api.us-west-2.amazonaws.com/graphql"
           },
           "arn": "arn:aws:appsync:us-west-2:348581070237:apis/abcdefghijklmnopqrstuvwxyz"
       }
   }
   ```

------

API 密钥可配置为最多 365 天，并且您可以将现有到期日期再延长多达 365 天（从到期日期当天开始）。建议将 API 密钥用于开发目的或可以安全公开公有 API 的使用案例。

在客户端，通过标头 `x-api-key` 指定 API 密钥。

例如，如果您的 `API_KEY` 为 `'ABC123'`，则您可以通过 `curl` 发送 GraphQL 查询，如下所示：

```
$ curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:ABC123" -d '{ "query": "query { movies { id } }" }' https://YOURAPPSYNCENDPOINT/graphql
```

## AWS\$1LAMBDA 授权
<a name="aws-lambda-authorization"></a>

您可以使用 AWS Lambda 函数实现自己的 API 授权逻辑。您可以将 Lambda 函数用于主要或辅助授权者，但每个 API 只能有一个 Lambda 授权函数。在使用 Lambda 函数进行授权时，以下限制适用：
+ 如果 API 启用了 `AWS_LAMBDA` 和 `AWS_IAM` 授权模式，则不能将 SigV4 签名作为 `AWS_LAMBDA` 授权令牌。
+ 如果 API 启用了 `AWS_LAMBDA` 和 `OPENID_CONNECT` 授权模式或 `AMAZON_COGNITO_USER_POOLS` 授权模式，则不能将 OIDC 令牌作为 `AWS_LAMBDA` 授权令牌。请注意，OIDC 令牌可以采用持有者方案。
+ Lambda 函数不能为解析器返回超过 5MB 的上下文数据。

例如，如果您的授权令牌是 `'ABC123'`，您可以通过 curl 发送 GraphQL 查询，如下所示：

```
$ curl -XPOST -H "Content-Type:application/graphql" -H "Authorization:ABC123" -d '{ "query":
         "query { movies { id } }" }' https://YOURAPPSYNCENDPOINT/graphql
```

Lambda 函数是在每个查询或变更之前调用的。可以根据 API ID 和身份验证令牌缓存返回值。当 Lambda 授权方响应小于 1,048,576 字节时，会缓存该响应以供后续请求使用。 AWS AppSync 如果 Lambda 授权方响应等于或大于 1,048,576 字节，则 AWS AppSync 不会缓存响应，而是为每个传入的请求调用 Lambda 授权器。为了优化性能并更大限度地降低 Lambda 调用成本，我们建议将 Lambda 授权方响应限制在 1,048,576 字节以内。默认情况下，不会开启缓存，但可以在 API 级别启用该功能，或者在函数的返回值中设置 `ttlOverride` 值以启用该功能。

如果需要，可以指定一个正则表达式，以在调用函数之前验证授权令牌。这些正则表达式用于在调用函数之前验证授权令牌格式是否正确。将自动拒绝使用的令牌与该正则表达式不匹配的任何请求。

用于授权的 Lambda 函数需要对其应用委托人策略 AWS AppSync 才能允许调用它们。`appsync.amazonaws.com`此操作在 AWS AppSync 控制台中自动完成； AWS AppSync 控制台*不会*删除该策略。有关向 Lambda 函数附加策略的更多信息，[请参阅开发人员指南中的基于资源的](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html#permissions-resource-serviceinvoke)策略。 AWS Lambda 

您指定的 Lambda 函数将收到具有以下形状的事件：

```
{
    "authorizationToken": "ExampleAUTHtoken123123123",
    "requestContext": {
        "apiId": "aaaaaa123123123example123",
        "accountId": "111122223333",
        "requestId": "f4081827-1111-4444-5555-5cf4695f339f",
        "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n",
        "operationName": "MyQuery",
        "variables": {}
    }
    "requestHeaders": {
        application request headers
    }
}
```

该`event`对象包含应用程序客户端在请求中发送到的标头 AWS AppSync。

授权函数必须至少返回一个布尔值`isAuthorized`，表示请求是否获得授权。 AWS AppSync 识别从 Lambda 授权函数返回的以下密钥：

**注意**  
`operationName`中用于 WebSocket 连接操作`requestContext`的值设置 AWS AppSync 为 “`DeepDish:Connect`”。

### 函数列表
<a name="aws-lambda-authorization-list"></a>

`isAuthorized`（布尔值，必需）  
一个布尔值，用于指示是否授权 `authorizationToken` 中的值调用 GraphQL API。  
如果该值为 true，则继续执行 GraphQL API。如果该值为 false，则会引发 `UnauthorizedException`。

`deniedFields`（字符串列表，可选）  
强制更改为 `null` 的值列表，即使从解析器中返回值也是如此。  
每个项目是 `arn:aws:appsync:us-east-1:111122223333:apis/GraphQLApiId/types/TypeName/fields/FieldName` 格式的完全限定字段 ARN，或者是 `TypeName.FieldName` 缩写格式。当两者 APIs 共享一个 Lambda 函数授权者并且两者之间的常见类型和字段之间可能存在歧义时，应使用完整的 ARN 表单。 APIs

`resolverContext`（JSON 对象，可选）  
在解析器模板中显示为 `$ctx.identity.resolverContext` 的 JSON 对象。例如，如果解析器返回以下结构：  

```
{
  "isAuthorized":true
  "resolverContext": {
    "banana":"very yellow",
    "apple":"very green" 
  }
}
```
解析器模板中的 `ctx.identity.resolverContext.apple` 值将为 "`very green`"。`resolverContext` 对象仅支持键值对。不支持嵌套的键。  
该 JSON 对象的总大小不能超过 5MB。

`ttlOverride`（整数，可选）  
应缓存响应的秒数。如果未返回任何值，则使用 API 中的值。如果为 0，则不会缓存响应。

Lambda 授权者的标准超时时间为 10 秒，但在流量高峰条件下可能会提前超时。我们建议将函数设计为在尽可能短的时间内（小于 1 秒）执行，以扩展 API 的性能。

多个 AWS AppSync APIs 可以共享一个身份验证 Lambda 函数。不允许跨账户授权者使用。

在多个字段之间共享授权功能时 APIs，请注意，短格式的字段名称 (`typename.fieldname`) 可能会无意中隐藏字段。要消除 `deniedFields` 中的字段的歧义，您可以使用 `arn:aws:appsync:region:accountId:apis/GraphQLApiId/types/typeName/fields/fieldName` 格式指定明确的字段 ARN。

要在 AWS AppSync中添加 Lambda 函数以作为默认授权模式，请执行以下操作：

------
#### [ Console ]

1. 登录 AWS AppSync 控制台并导航到您要更新的 API。

1. 导航到您的 API 的“设置”页面。

   将 API 级别授权更改为 **AWS Lambda**。

1. 选择 AWS 区域 和 Lambda ARN 来授权 API 调用。
**注意**  
将自动添加相应的主体策略，从而允许 AWS AppSync 调用您的 Lambda 函数。

1. （可选）设置响应 TTL 和令牌验证正则表达式。

------
#### [ AWS CLI ]

1. 将以下策略附加到使用的 Lambda 函数：

   ```
   aws lambda add-permission --function-name "my-function" --statement-id "appsync" --principal appsync.amazonaws.com --action lambda:InvokeFunction --output text 
   ```
**重要**  
如果您希望将函数策略锁定到单个 GraphQL API，您可以运行以下命令：  

   ```
   aws lambda add-permission --function-name “my-function” --statement-id “appsync” --principal appsync.amazonaws.com --action lambda:InvokeFunction --source-arn “<my AppSync API ARN>” --output text
   ```

1. 更新您的 AWS AppSync API 以使用给定的 Lambda 函数 ARN 作为授权方：

   ```
   aws appsync update-graphql-api --api-id example2f0ur2oid7acexample --name exampleAPI --authentication-type AWS_LAMBDA --lambda-authorizer-config authorizerUri="arn:aws:lambda:us-east-2:111122223333:function:my-function"
   ```
**注意**  
您还可以包含其他配置选项，例如令牌正则表达式。

------

以下示例介绍了一个 Lambda 函数，以说明 Lambda 函数在作为 AWS AppSync 授权机制时可能具有的各种身份验证和失败状态：

```
def handler(event, context):
  # This is the authorization token passed by the client
  token = event.get('authorizationToken')
  # If a lambda authorizer throws an exception, it will be treated as unauthorized. 
  if 'Fail' in token:
    raise Exception('Purposefully thrown exception in Lambda Authorizer.')

  if 'Authorized' in token and 'ReturnContext' in token:
    return {
      'isAuthorized': True,
      'resolverContext': {
        'key': 'value'
      }
    }

  # Authorized with no f
  if 'Authorized' in token:
    return {
      'isAuthorized': True
    }
  # Partial authorization
  if 'Partial' in token:
    return {
      'isAuthorized': True,
      'deniedFields':['user.favoriteColor']
    }
  if 'NeverCache' in token:
    return {
      'isAuthorized': True,
      'ttlOverride': 0
    }
  if 'Unauthorized' in token:
    return {
      'isAuthorized': False
    }
  # if nothing is returned, then the authorization fails. 
  return {}
```

### 规避 SigV4 和 OIDC 令牌授权限制
<a name="aws-lambda-authorization-create-new-auth-token"></a>

可以使用以下方法规避启用某些授权模式时无法将 SigV4 签名或 OIDC 令牌作为 Lambda 授权令牌的问题。

如果要在为 AWS AppSync API 启用了 `AWS_IAM` 和 `AWS_LAMBDA` 授权模式时将 SigV4 签名作为 Lambda 授权令牌，请执行以下操作：
+ 要创建新的 Lambda 授权令牌，请在 SigV4 签名中添加随机后缀 and/or 前缀。
+ 要检索原始 SigV4 签名，请从 Lambda 授权令牌中删除随机前缀后缀 and/or ，从而更新您的 Lambda 函数。然后，使用原始 SigV4 签名进行身份验证。

如果要在的 API 启用授权模式或`AMAZON_COGNITO_USER_POOLS`和授权模式时使用 OIDC 令牌作为 Lambda `AWS_LAMBDA` 授权令牌，请执行以下操作：`OPENID_CONNECT` AWS AppSync
+ 要创建新的 Lambda 授权令牌，请向 OIDC 令牌添加随机后缀 and/or 前缀。Lambda 授权令牌不应包含持有者方案前缀。
+ 要检索原始 OIDC 令牌，请从 Lambda 授权令牌中删除随机前缀后缀 and/or ，从而更新您的 Lambda 函数。然后，使用原始 OIDC 令牌进行身份验证。

## AWS\$1IAM 授权
<a name="aws-iam-authorization"></a>

该授权类型对 GraphQL API 强制执行 [AWS 签名版本 4 签名过程](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)。您可以将 Identity and Access Management ([IAM](https://aws.amazon.com/iam/)) 访问策略与此授权类型关联。您的应用程序可以使用访问密钥（由访问密钥 ID 和秘密访问密钥组成）或 Amazon Cognito 联合身份提供的短期临时凭证以利用该关联。

如果您希望有访问权限的角色执行所有数据操作：

------
#### [ JSON ]

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement": [
      {
         "Effect": "Allow",
         "Action": [
            "appsync:GraphQL"
         ],
         "Resource": [
            "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/*"
         ]
      }
   ]
}
```

------

你可以在 AppSync控制`YourGraphQLApiId`台的主API列表页面找到你的 API 名称正下方。或者，您也可以使用 CLI 检索它：`aws appsync list-graphql-apis`

如果您只想限制对某些 GraphQL 操作的访问，您可以对根 `Query`、`Mutation` 和 `Subscription` 字段执行此操作。

------
#### [ JSON ]

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement": [
      {
         "Effect": "Allow",
         "Action": [
            "appsync:GraphQL"
         ],
         "Resource": [
            "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-1>",
            "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-2>",
            "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Mutation/fields/<Field-1>",
            "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Subscription/fields/<Field-1>"
         ]
     }
   ]
}
```

------

例如，假设您具有以下架构，并且您想要限制能够获取所有文章的访问权限：

```
schema {
   query: Query
   mutation: Mutation
}

type Query {
   posts:[Post!]!
}

type Mutation {
   addPost(id:ID!, title:String!):Post!
}
```

角色的相应 IAM 策略（例如，您可以将其附加到 Amazon Cognito 身份池）将如下所示：

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
            "appsync:GraphQL"
            ],
            "Resource": [
                "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/posts"
            ]
        }
    ]
}
```

------

## OPENID\$1CONNECT 授权
<a name="openid-connect-authorization"></a>

该授权类型强制实施 OIDC 兼容服务提供的 [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) (OIDC) 令牌。您的应用程序可以利用由 OIDC 提供程序定义的用户和权限来控制访问。

发布者 URL 是由您提供给 AWS AppSync 的唯一需要的配置值，例如 `https://auth.example.com`。此网址必须可通过 HTTPS 进行寻址。 AWS AppSync [附加`/.well-known/openid-configuration`到颁发者网址并按照 OpenID Connect Discovery 规范找到 `https://auth.example.com/.well-known/openid-configuration` OpenID 配置。](https://openid.net/specs/openid-connect-discovery-1_0.html)它希望通过此网址检索[RFC5785](https://tools.ietf.org/html/rfc5785)合规的 JSON 文档。此 JSON 文档必须包含一个`jwks_uri`密钥，该密钥指向带有签名密钥的 JSON Web 密钥集 (JWKS) 文档。 AWS AppSync 要求 JWKS 包含和的 JSON 字段。`kty` `kid`

AWS AppSync 支持多种签名算法。


| 签名算法 | 
| --- | 
| RS256 | 
| RS384 | 
| RS512 | 
| PS256 | 
| PS384 | 
| PS512 | 
| HS256 | 
| HS384 | 
| HS512 | 
| ES256 | 
| ES384 | 
| ES512 | 

我们建议您使用 RSA 算法。提供程序颁发的令牌必须包括颁发令牌的时间 (`iat`)，并可能包含对其进行身份验证的时间 (`auth_time`)。您可以在 OpenID Connect 配置中为颁发时间 (`iatTTL`) 和身份验证时间 (`authTTL`) 提供 TTL 值以进行额外的验证。如果您的提供程序授权多个应用程序，还可以提供一个用于按客户端 ID 进行授权的正则表达式 (`clientId`)。当您`clientId`的 OpenID Connect 配置中存在时，通过要求与令牌中的`aud`或`azp`声明匹配`clientId`来 AWS AppSync 验证声明。

要验证多个客户端， IDs 请使用管道运算符 (“\$1”)，这是正则表达式中的 “或”。例如，如果您的 OIDC 应用程序有四个客户端，其客户端（ IDs例如 0A1S2D、1F4G9H、1J6L4B、6 GS5 MG）仅验证前三个客户端，则应在客户端 IDs ID 字段中放置 1F4G9H\$11J6L4 GS5 B\$16 MG。

如果 API 配置了多种授权类型，则通过将请求标头中的 JWT 令牌中存在的颁发者（iss 声明）与 API 配置中指定的颁发者 URL 进行比较，来 AWS AppSync 验证请求标头中存在的颁发者（iss 声明）。但是，当 API 仅配置OPENID\$1CONNECT 授权时，会 AWS AppSync 跳过此颁发者 URL 验证步骤。

## AMAZON\$1COGNITO\$1USER\$1POOLS 授权
<a name="amazon-cognito-user-pools-authorization"></a>

该授权类型强制实施 Amazon Cognito 用户池提供的 OIDC 令牌。您的应用程序可以利用您的用户池和其他 AWS 账户的用户池中的用户和群组，并将它们与 GraphQL 字段关联以控制访问权限。

在使用 Amazon Cognito 用户池时，您可以创建用户所属的组。此信息以 JWT 令牌编码，您的应用程序在发送 GraphQL 操作时将其发送到授权标头 AWS AppSync 中。您可以在架构上使用 GraphQL 指令以控制哪些组可以对字段调用哪些解析器，从而向客户提供更受控制的访问。

例如，假设您具有以下 GraphQL 架构：

```
schema {
   query: Query
   mutation: Mutation
}

type Query {
   posts:[Post!]!
}

type Mutation {
   addPost(id:ID!, title:String!):Post!
}
...
```

如果您在 Amazon Cognito 用户池中具有两个组（bloggers 和 readers），并希望限制读者以使他们无法添加新条目，则架构应如下所示：

```
schema {
   query: Query
   mutation: Mutation
}
```

```
type Query {
   posts:[Post!]!
   @aws_auth(cognito_groups: ["Bloggers", "Readers"])
}

type Mutation {
   addPost(id:ID!, title:String!):Post!
   @aws_auth(cognito_groups: ["Bloggers"])
}
...
```

请注意，如果您想默认使用特定的访问 grant-or-deny策略，则可以省略该`@aws_auth`指令。通过控制台或以下 CLI 命令创建 GraphQL API 时，可以在用户池配置中指定 grant-or-deny策略：

```
$ aws appsync --region us-west-2 create-graphql-api --authentication-type AMAZON_COGNITO_USER_POOLS  --name userpoolstest --user-pool-config '{ "userPoolId":"test", "defaultEffect":"ALLOW", "awsRegion":"us-west-2"}'
```

## 使用其他授权模式
<a name="using-additional-authorization-modes"></a>

添加其他授权模式时，可以直接在 AWS AppSync GraphQL API 级别配置授权设置（即可以直接在`GraphqlApi`对象上配置的`authenticationType`字段），它充当架构上的默认设置。这意味着任何没有特定指令的类型都必须通过 API 级别授权设置。

在架构级别，您可以使用架构上的指令指定其他授权模式。您可以在架构中的各个字段上指定授权模式。例如，对于 `API_KEY` 授权，您将在架构对象类型定义/字段上使用 `@aws_api_key`。架构字段和对象类型定义支持以下指令：
+  `@aws_api_key` - 指定字段是 `API_KEY` 授权的。
+  `@aws_iam` - 指定字段是 `AWS_IAM` 授权的。
+  `@aws_oidc` - 指定字段是 `OPENID_CONNECT` 授权的。
+  `@aws_cognito_user_pools` - 指定字段是 `AMAZON_COGNITO_USER_POOLS` 授权的。
+  `@aws_lambda` - 指定字段是 `AWS_LAMBDA` 授权的。

您不能将 `@aws_auth` 指令与其他授权模式一起使用。`@aws_auth` 仅适用于 `AMAZON_COGNITO_USER_POOLS` 授权的上下文，没有其他授权模式。但是，您可以使用 `@aws_cognito_user_pools` 指令代替 `@aws_auth` 指令，使用相同的参数。两者之间的主要区别在于您可以在任何字段和对象类型定义上指定 `@aws_cognito_user_pools`。

要了解其他授权模式如何工作以及如何在架构上指定它们，我们来看一下以下架构：

```
schema {
   query: Query
   mutation: Mutation
}

type Query {
   getPost(id: ID): Post
   getAllPosts(): [Post]
   @aws_api_key
}

type Mutation {
   addPost(
      id: ID!
      author: String!
      title: String!
      content: String!
      url: String!
   ): Post!
}

type Post @aws_api_key @aws_iam {
   id: ID!
   author: String
   title: String
   content: String
   url: String
   ups: Int!
   downs: Int!
   version: Int!
}
...
```

对于此架构，假设这`AWS_IAM`是 AWS AppSync GraphQL API 上的默认授权类型。这意味着使用 `AWS_IAM` 保护没有指令的字段。例如，`Query` 类型上的 `getPost` 字段就是这种情况。架构指令使您可以使用多种授权模式。例如，您可以在 AWS AppSync GraphQL API 上`API_KEY`配置为额外的授权模式，也可以使用`@aws_api_key`指令标记字段（例如，在本示例`getAllPosts`中）。指令在字段级别工作，因此您也需要授予 `API_KEY` 访问 `Post` 类型的权限。您可以通过使用指令标记 `Post` 类型中的每个字段，或使用 `@aws_api_key` 指令标记 `Post` 类型来执行此操作。

要进一步限制对 `Post` 类型中的字段的访问，可以对 `Post` 类型中的各个字段使用指令，如下所示。

例如，您可以将 `restrictedContent` 字段添加到 `Post` 类型并使用 `@aws_iam` 指令限制对它的访问。 `AWS_IAM` 经过身份验证的请求可以访问 `restrictedContent`，但是，`API_KEY` 请求将无法访问它。

```
type Post @aws_api_key @aws_iam{
   id: ID!
   author: String
   title: String
   content: String
   url: String
   ups: Int!
   downs: Int!
   version: Int!
   restrictedContent: String!
   @aws_iam
}
...
```

## 精细访问控制
<a name="fine-grained-access-control"></a>

上述信息演示了如何限制或授权对某些 GraphQL 字段的访问权限。如果您想根据某些条件（例如，根据进行调用的用户以及用户是否拥有数据）来设置对数据的访问控制，则可以使用解析器中的映射模板。您还可以执行更复杂的业务逻辑，[筛选信息](#aws-appsync-filtering-information)中介绍了相关内容。

本节介绍了如何使用 DynamoDB 解析器映射模板设置数据的访问控制。

在继续操作之前，如果您不熟悉中的 AWS AppSync映射模板，则可能需要查看 DynamoDB 的[解析器映射模板参考](resolver-mapping-template-reference.md#aws-appsync-resolver-mapping-template-reference)和 [DynamoDB 的解析器映射模板参考](resolver-mapping-template-reference-dynamodb.md#aws-appsync-resolver-mapping-template-reference-dynamodb)。

在使用 DynamoDB 的以下示例中，假设您使用前面的博客文章架构，并且仅允许创建博客的用户对其进行编辑。评估过程将是用户在应用程序中获得凭证（例如，使用 Amazon Cognito 用户池），然后将这些凭证作为 GraphQL 操作的一部分进行传递。然后，映射模板将替换条件语句中凭证（如用户名）的值，该值随后将与数据库中的值进行比较。

![\[Diagram showing authentication flow from user login to database operation using AWS 服务.\]](http://docs.aws.amazon.com/zh_cn/appsync/latest/devguide/images/FGAC.png)


要添加此功能，请添加 GraphQL 字段 `editPost`，如下所示：

```
schema {
   query: Query
   mutation: Mutation
}

type Query {
   posts:[Post!]!
}

type Mutation {
   editPost(id:ID!, title:String, content:String):Post
   addPost(id:ID!, title:String!):Post!
}
...
```

`editPost` 的解析器映射模板（本节末尾的示例中所示）需要针对您的数据存储执行逻辑检查，以仅允许创建文章的用户对文章进行编辑。由于这是一个编辑操作，因此，它对应于 DynamoDB 中的 `UpdateItem`。您可以先执行条件检查，然后再执行此操作，同时使用传递的上下文进行用户身份验证。这些信息存储在一个 `Identity` 对象中，该对象具有以下值：

```
{
   "accountId" : "12321434323",
   "cognitoIdentityPoolId" : "",
   "cognitoIdentityId" : "",
   "sourceIP" : "",
   "caller" : "ThisistheprincipalARN",
   "username" : "username",
   "userArn" : "Sameasabove"
}
```

要在 DynamoDB `UpdateItem` 调用中使用该对象，您需要在表中存储用户身份信息以进行比较。首先，您的 `addPost` 变更需要存储创建者。其次，您的 `editPost` 变更需要先执行条件检查，然后才能更新。

下面是 `addPost` 的解析器代码示例，用于将用户身份存储为 `Author` 列：

```
import { util, Context } from '@aws-appsync/utils';
import { put } from '@aws-appsync/utils/dynamodb';

export function request(ctx) {
	const { id: postId, ...item } = ctx.args;
	return put({
		key: { postId },
		item: { ...item, Author: ctx.identity.username },
		condition: { postId: { attributeExists: false } },
	});
}

export const response = (ctx) => ctx.result;
```

请注意，`Author` 属性通过 `Identity` 对象填充，该对象来自应用程序。

最后，此处的示例介绍了 `editPost` 的解析器代码，此代码仅当请求来自创建博客文章的用户时，才更新该博客文章的内容。

```
import { util, Context } from '@aws-appsync/utils';
import { put } from '@aws-appsync/utils/dynamodb';

export function request(ctx) {
	const { id, ...item } = ctx.args;
	return put({
		key: { id },
		item,
		condition: { author: { contains: ctx.identity.username } },
	});
}

export const response = (ctx) => ctx.result;
```

此示例使用 `PutItem` 覆盖所有值，而不是使用 `UpdateItem`，但同样的概念适用于 `condition` 语句块。

## 筛选信息
<a name="aws-appsync-filtering-information"></a>

有时，在成功写入或读取数据来源时，您可能无法控制来自数据来源的响应，但又不想向客户端发送不必要的信息。在这些情况下，您可以使用响应映射模板筛选信息。

例如，假设您的博客文章 DynamoDB 表上没有相应的索引（例如 `Author` 上的索引），您可以使用以下解析器：

```
import { util, Context } from '@aws-appsync/utils';
import { get } from '@aws-appsync/utils/dynamodb';

export function request(ctx) {
	return get({ key: { ctx.args.id } });
}

export function response(ctx) {
	if (ctx.result.author === ctx.identity.username) {
		return ctx.result;
	}
	return null;
}
```

即使调用方不是创建博客文章的作者，请求处理程序也会获取该项目。为了防止它返回所有数据，响应处理程序会进行检查，以确保调用方与项目的作者匹配。如果调用方与此检查不匹配，则只返回 Null 响应。

## 数据来源访问
<a name="data-source-access"></a>

AWS AppSync 使用身份和访问管理 ([IAM](https://aws.amazon.com/iam/)) 角色和访问策略与数据源通信。如果您使用的是现有角色，则需要添加信任策略才能代 AWS AppSync 入该角色。该信任关系将类似于下面内容：

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

需要将角色的访问策略缩小为仅具有对必要的最少资源集进行操作的权限，这一点非常重要。使用 AppSync 控制台创建数据源和创建角色时，系统会自动为您完成此操作。但是，当使用 IAM 控制台中的内置示例模板在 AWS AppSync 控制台之外创建角色时，权限不会自动限定在某个资源上，您应该在将应用程序转移到生产之前执行此操作。

# 保护请求和响应的访问控制使用案例
<a name="security-authorization-use-cases"></a>

在[安全](security-authz.md#aws-appsync-security)部分，您了解了保护 API 的不同授权模式，这一部分还介绍了精细授权机制，以便于您理解概念和流程。由于 AWS AppSync 允许您通过使用 GraphQL Resolver Mapping [模板对](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview)数据执行逻辑完整操作，因此您可以使用用户身份、条件和数据注入的组合以非常灵活的方式保护读取或写入数据。

如果您不熟悉编辑 AWS AppSync Resolvers，请查看[编程指南](resolver-mapping-template-reference-programming-guide.md#aws-appsync-resolver-mapping-template-reference-programming-guide)。

## 概述
<a name="overview"></a>

在系统中授予数据访问权限的传统方法是通过[访问控制矩阵](https://en.wikipedia.org/wiki/Access_Control_Matrix)，其中行（资源）和列（用户/角色）的交叉点就是授予的权限。

AWS AppSync 使用您自己账户中的资源，并将身份（用户/角色）信息作为上下文对象传递到 GraphQL 请求和响应中，您可以在解析器中使用该[上下文对象](resolver-context-reference.md#aws-appsync-resolver-mapping-template-context-reference)。也就是说，可以根据解析器的逻辑对读取或写入操作授予适当的权限。如果此逻辑位于资源级别，例如，只有某些指定用户或组可以 read/write 访问特定的数据库行，则必须存储该 “授权元数据”。 AWS AppSync 不存储任何数据，因此您必须将此授权元数据与资源一起存储，以便计算权限。授权元数据通常是 DynamoDB 表中的一个属性（列），例如 **owner** 或用户/组的列表。例如，可能有**读取者**和**写入者**属性。

从宏观角度而言，这就意味着如果您要从数据来源中读取单个项目，即在解析器从数据来源中读取内容之后在响应模板中执行一个条件 `#if () ... #end` 语句。此项检查通常会针对读取操作返回的授权元数据使用 `$context.identity` 中的用户或组值进行成员资格检查。如有多条记录，例如 `Scan` 或 `Query` 表返回的列表，您将使用类似的用户或组值将条件检查作为操作的一部分发送到数据来源。

同样，在写入数据时，您将对操作（如 `PutItem` 或 `UpdateItem`）应用条件语句，以便了解进行变更的用户或组是否拥有权限。在许多情况下，条件将使用 `$context.identity` 中的值与资源的授权元数据进行比较。对于请求和响应模板，您还可使用客户端的自定义标头进行验证检查。

## 读取数据
<a name="reading-data"></a>

如上所述，执行检查的授权元数据必须随资源存储，或传递到 GraphQL 请求中（身份、标头等）。为了说明这一点，假设您有如下 DynamoDB 表：

![\[DynamoDB table with ID, Data, PeopleCanAccess, GroupsCanAccess, and Owner columns.\]](http://docs.aws.amazon.com/zh_cn/appsync/latest/devguide/images/auth.png)


主键是 `id`，要访问的数据是 `Data`。其他列是您可以执行的授权检查示例。`Owner` 是 `String`，而 `PeopleCanAccess` 和 `GroupsCanAccess` 是 `String Sets`，如 [DynamoDB 解析器映射模板参考](resolver-mapping-template-reference-dynamodb.md#aws-appsync-resolver-mapping-template-reference-dynamodb)中所述。

[解析器映射模板概述](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview)中的示意图展示了响应模板中不仅包含上下文对象，还包含数据来源的结果。对于个别项目的 GraphQL 查询，您可以使用响应模板检查是否允许用户查看这些结果，或返回授权错误消息。这种方法有时称为”授权筛选“。对于 GraphQL 查询返回列表，更高效的方式是使用 Scan 或 Query 针对请求模板执行检查，只在满足授权条件的情况下返回数据。实施方法是：

1. GetItem -个人记录的授权检查。使用 `#if() ... #end` 语句实现。

1. Scan/Query 操作 - 授权检查是 `"filter":{"expression":...}` 语句。常用的检查方式是等式 (`attribute = :input`) 或者检查某个值是否在列表中 (`contains(attribute, :input)`)。

在 \$12 中，两条语句中的 `attribute` 表示表中记录的列名，例如上例中的 `Owner`。您可以借助 `#` 符号并使用 `"expressionNames":{...}` 设置别名，但这不是必需的。`:input` 可引用与数据库属性进行比较的值，该属性在 `"expressionValues":{...}` 中定义。您将在下文中看到这些示例。

### 使用案例：所有者可以读取
<a name="use-case-owner-can-read"></a>

以上表为例，对于一个读取操作 (`Owner == Nadia`)，如果您希望只在 `GetItem` 的情况下返回数据，您的模板将如下所示：

```
#if($context.result["Owner"] == $context.identity.username)
    $utils.toJson($context.result)
#else
    $utils.unauthorized()
#end
```

这里要提醒您几件事，在接下来的各节也会用到。首先，检查使用 `$context.identity.username`，如果使用 Amazon Cognito 用户池，这是友好的用户注册名称；如果使用 IAM，这是用户身份（包括 Amazon Cognito 联合身份）。还可以为所有者存储其他值，例如唯一的“Amazon Cognito 身份”值，这在从多个位置进行联合登录时是非常有用的，您应该查看[解析器映射模板上下文参考](resolver-context-reference.md#aws-appsync-resolver-mapping-template-context-reference)中提供的选项。

第二，利用 `$util.unauthorized()` 进行响应的条件 else 检查完全是可选的，但作为最佳实践，建议您在设计 GraphQL API 时使用。

### 使用案例：硬编码特定的访问权限
<a name="use-case-hardcode-specific-access"></a>

```
// This checks if the user is part of the Admin group and makes the call
#foreach($group in $context.identity.claims.get("cognito:groups"))
    #if($group == "Admin")
        #set($inCognitoGroup = true)
    #end
#end
#if($inCognitoGroup)
{
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    },
    "attributeValues" : {
        "owner" : $util.dynamodb.toDynamoDBJson($context.identity.username)
        #foreach( $entry in $context.arguments.entrySet() )
            ,"${entry.key}" : $util.dynamodb.toDynamoDBJson($entry.value)
        #end
    }
}
#else
    $utils.unauthorized()
#end
```

### 使用案例：筛选结果列表
<a name="use-case-filtering-a-list-of-results"></a>

在上一示例中，您能够直接针对 `$context.result` 执行检查，因为它只会返回一个项目；但有些操作（如扫描）将在 `$context.result.items` 中返回多个项目，您需要执行授权筛选，仅返回允许用户看到的结果。假设 `Owner` 字段这次在记录上设置了 Amazon Cognito IdentityID，则可以使用以下响应映射模板进行筛选，以仅显示用户拥有的记录：

```
#set($myResults = [])
#foreach($item in $context.result.items)
    ##For userpools use $context.identity.username instead
    #if($item.Owner == $context.identity.cognitoIdentityId)
        #set($added = $myResults.add($item))
    #end
#end
$utils.toJson($myResults)
```

### 使用案例：多人可以读取
<a name="use-case-multiple-people-can-read"></a>

另一种常用的授权选项是允许一组人员读取数据。在以下示例中，只有在运行 GraphQL 查询的用户属于 `"filter":{"expression":...}` 集的情况下 `PeopleCanAccess` 才会返回扫描表的值。

```
{
    "version" : "2017-02-28",
    "operation" : "Scan",
    "limit": #if(${context.arguments.count}) $util.toJson($context.arguments.count) #else 20 #end,
    "nextToken": #if(${context.arguments.nextToken})  $util.toJson($context.arguments.nextToken) #else null #end,
    "filter":{
        "expression": "contains(#peopleCanAccess, :value)",
        "expressionNames": {
                "#peopleCanAccess": "peopleCanAccess"
        },
        "expressionValues": {
                ":value": $util.dynamodb.toDynamoDBJson($context.identity.username)
        }
    }
}
```

### 使用案例：组可以读取
<a name="use-case-group-can-read"></a>

与上一使用案例类似，可能只有一个或多个组中的人员才有权读取数据库中的某些项目。使用 `"expression": "contains()"` 操作具有类似效果，但在集的成员资格中，需考虑用户所在的所有组之间是逻辑或的关系。在本例中，我们构建一个 `$expression` 语句，包含用户所在的每个组，然后传递给筛选器：

```
#set($expression = "")
#set($expressionValues = {})
#foreach($group in $context.identity.claims.get("cognito:groups"))
    #set( $expression = "${expression} contains(groupsCanAccess, :var$foreach.count )" )
    #set( $val = {})
    #set( $test = $val.put("S", $group))
    #set( $values = $expressionValues.put(":var$foreach.count", $val))
    #if ( $foreach.hasNext )
    #set( $expression = "${expression} OR" )
    #end
#end
{
    "version" : "2017-02-28",
    "operation" : "Scan",
    "limit": #if(${context.arguments.count}) $util.toJson($context.arguments.count) #else 20 #end,
    "nextToken": #if(${context.arguments.nextToken})  $util.toJson($context.arguments.nextToken) #else null #end,
    "filter":{
        "expression": "$expression",
        "expressionValues": $utils.toJson($expressionValues)
    }
}
```

## 写入数据
<a name="writing-data"></a>

将数据写入变更始终是通过请求映射模板进行控制的。对于 DynamoDB 数据来源而言，关键是要使用适当的 `"condition":{"expression"...}"`，针对表中的授权元数据执行验证。[安全](security-authz.md#aws-appsync-security)部分提供了一个示例，您可以使用它检查表中的 `Author` 字段。本节中将探索更多使用案例。

### 使用案例：多个所有者
<a name="use-case-multiple-owners"></a>

使用之前的表示意图示例，假设 `PeopleCanAccess` 列表

```
{
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    },
    "update" : {
        "expression" : "SET meta = :meta",
        "expressionValues": {
            ":meta" : $util.dynamodb.toDynamoDBJson($ctx.args.meta)
        }
    },
    "condition" : {
        "expression"       : "contains(Owner,:expectedOwner)",
        "expressionValues" : {
            ":expectedOwner" : $util.dynamodb.toDynamoDBJson($context.identity.username)
        }
    }
}
```

### 使用案例：组可以创建新记录
<a name="use-case-group-can-create-new-record"></a>

```
#set($expression = "")
#set($expressionValues = {})
#foreach($group in $context.identity.claims.get("cognito:groups"))
    #set( $expression = "${expression} contains(groupsCanAccess, :var$foreach.count )" )
    #set( $val = {})
    #set( $test = $val.put("S", $group))
    #set( $values = $expressionValues.put(":var$foreach.count", $val))
    #if ( $foreach.hasNext )
    #set( $expression = "${expression} OR" )
    #end
#end
{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        ## If your table's hash key is not named 'id', update it here. **
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
        ## If your table has a sort key, add it as an item here. **
    },
    "attributeValues" : {
        ## Add an item for each field you would like to store to Amazon DynamoDB. **
        "title" : $util.dynamodb.toDynamoDBJson($ctx.args.title),
        "content": $util.dynamodb.toDynamoDBJson($ctx.args.content),
        "owner": $util.dynamodb.toDynamoDBJson($context.identity.username)
    },
    "condition" : {
        "expression": $util.toJson("attribute_not_exists(id) AND $expression"),
        "expressionValues": $utils.toJson($expressionValues)
    }
}
```

### 使用案例：组可以更新现有记录
<a name="use-case-group-can-update-existing-record"></a>

```
#set($expression = "")
#set($expressionValues = {})
#foreach($group in $context.identity.claims.get("cognito:groups"))
    #set( $expression = "${expression} contains(groupsCanAccess, :var$foreach.count )" )
    #set( $val = {})
    #set( $test = $val.put("S", $group))
    #set( $values = $expressionValues.put(":var$foreach.count", $val))
    #if ( $foreach.hasNext )
    #set( $expression = "${expression} OR" )
    #end
#end
{
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    },
    "update":{
                "expression" : "SET title = :title, content = :content",
        "expressionValues": {
            ":title" : $util.dynamodb.toDynamoDBJson($ctx.args.title),
            ":content" : $util.dynamodb.toDynamoDBJson($ctx.args.content)
        }
    },
    "condition" : {
        "expression": $util.toJson($expression),
        "expressionValues": $utils.toJson($expressionValues)
    }
}
```

## 公有记录和私有记录
<a name="public-and-private-records"></a>

您还可利用条件筛选器选择将数据标记为私有、公开，或进行其他布尔检查。还可在响应模板中进行组合，作为授权筛选的一部分。使用此检查是一种临时隐藏数据，使之不可见的好方法，而无需尝试控制组成员资格。

例如，假设您为 DynamoDB 表中的每个项目添加了一个称为 `public` 的属性，其值可为 `yes` 或 `no`。以下响应模板可用于 `GetItem` 调用，只在用户属于具有权限的组，且数据标为公共时才会显示数据：

```
#set($permissions = $context.result.GroupsCanAccess)
#set($claimPermissions = $context.identity.claims.get("cognito:groups"))

#foreach($per in $permissions)
    #foreach($cgroups in $claimPermissions)
        #if($cgroups == $per)
            #set($hasPermission = true)
        #end
    #end
#end

#if($hasPermission && $context.result.public == 'yes')
    $utils.toJson($context.result)
#else
    $utils.unauthorized()
#end
```

以上代码还可使用逻辑或 (`||`) 允许有权限的人员读取记录，或允许读取公共记录：

```
#if($hasPermission || $context.result.public == 'yes')
    $utils.toJson($context.result)
#else
    $utils.unauthorized()
#end
```

通常，在执行授权检查时，您会发现标准运算符 `==`、`!=`、`&&` 和 `||` 很有用。

## 实时数据
<a name="security-real-time-data"></a>

客户端进行订阅时，您可使用本文档之前介绍的方法，针对 GraphQL 订阅应用精细访问控制。您可将解析器附加到订阅字段，然后您就可以查询数据来源的数据，并在请求或响应映射模板中执行条件逻辑。您也可以将其他数据返回到客户端，例如订阅的最初结果，条件是数据结构与 GraphQL 订阅中返回类型的结构相匹配。

### 使用案例：用户只能订阅特定对话
<a name="use-case-user-can-subscribe-to-specific-conversations-only"></a>

GraphQL 订阅实时数据的一种常见使用案例是构建消息收发或私人聊天应用程序。如果创建具有多个用户的聊天应用程序，可以在两人或多人之间发生对话。这些对话可以用私有或公共“房间”进行组织。因此，您可能只希望授权用户订阅他们有权访问的对话（可能是一对一或小组对话）。出于演示目的，以下示例展示一个简单的使用案例：一名用户向另一名用户发送私人消息。该设置具有两个 Amazon DynamoDB 表：
+ 消息表：（主键）`toUser`，（排序键）`id` 
+ 权限表：（主键）`username` 

消息表存储通过 GraphQL 变更发送的实际消息。权限表用于 GraphQL 订阅在客户端连接时检查授权。以下示例假设您使用以下 GraphQL 架构：

```
input CreateUserPermissionsInput {
    user: String!
    isAuthorizedForSubscriptions: Boolean
}

type Message {
    id: ID
    toUser: String
    fromUser: String
    content: String
}

type MessageConnection {
    items: [Message]
    nextToken: String
}

type Mutation {
    sendMessage(toUser: String!, content: String!): Message
    createUserPermissions(input: CreateUserPermissionsInput!): UserPermissions
    updateUserPermissions(input: UpdateUserPermissionInput!): UserPermissions
}

type Query {
    getMyMessages(first: Int, after: String): MessageConnection
    getUserPermissions(user: String!): UserPermissions
}

type Subscription {
    newMessage(toUser: String!): Message
        @aws_subscribe(mutations: ["sendMessage"])
}

input UpdateUserPermissionInput {
    user: String!
    isAuthorizedForSubscriptions: Boolean
}

type UserPermissions {
    user: String
    isAuthorizedForSubscriptions: Boolean
}

schema {
    query: Query
    mutation: Mutation
    subscription: Subscription
}
```

下面未介绍某些标准操作（例如 `createUserPermissions()`）以说明订阅解析器，但它们是 DynamoDB 解析器的标准实施。我们关注的是利用解析器进行订阅授权的流程。要从一个用户向另一个用户发送消息，可将解析器附加到 `sendMessage()` 字段，并利用以下请求模板选择**消息**表数据来源：

```
{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        "toUser" : $util.dynamodb.toDynamoDBJson($ctx.args.toUser),
        "id" : $util.dynamodb.toDynamoDBJson($util.autoId())
    },
    "attributeValues" : {
        "fromUser" : $util.dynamodb.toDynamoDBJson($context.identity.username),
        "content" : $util.dynamodb.toDynamoDBJson($ctx.args.content),
    }
}
```

在此示例中，我们使用的是 `$context.identity.username`。这将返回亚马逊 Cognito 用户的用户信息。 AWS Identity and Access Management 响应模板只是简单地传递 `$util.toJson($ctx.result)`。保存并返回架构页面。然后为 `newMessage()` 订阅附加解析器，使用**权限**表作为数据来源，使用以下请求映射模板：

```
{
    "version": "2018-05-29",
    "operation": "GetItem",
    "key": {
        "username": $util.dynamodb.toDynamoDBJson($ctx.identity.username),
    },
}
```

然后利用**权限**表中的数据，通过以下响应映射模板执行授权检查：

```
#if(! ${context.result})
    $utils.unauthorized()
#elseif(${context.identity.username} != ${context.arguments.toUser})
    $utils.unauthorized()
#elseif(! ${context.result.isAuthorizedForSubscriptions})
    $utils.unauthorized()
#else
##User is authorized, but we return null to continue
    null
#end
```

在本例中，您进行三次授权检查。第一次确保返回结果。第二次确保用户未订阅面向其他人的消息。通过检查存储为 `BOOL` 的 `isAuthorizedForSubscriptions` DynamoDB 属性，第三次检查确保允许用户订阅任何字段。

要测试一下，你可以使用 Amazon Cognito 用户池和名为 “Nadia” 的用户登录 AWS AppSync 控制台，然后运行以下 GraphQL 订阅：

```
subscription AuthorizedSubscription {
    newMessage(toUser: "Nadia") {
        id
        toUser
        fromUser
        content
    }
}
```

如果**权限** 表中有一条 `username` 的记录，其键属性为 `Nadia`，`isAuthorizedForSubscriptions` 设置为 `true`，您将看到成功响应。如果您在以上 `username` 查询中尝试其他 `newMessage()`，将返回错误。

# AWS WAF 用来保护你的 AWS AppSync APIs
<a name="WAF-Integration"></a>

AWS WAF 是一种 Web 应用程序防火墙，可帮助保护 Web 应用程序和 APIs 免受攻击。通过使用该功能，您可以配置一组规则（称为 Web 访问控制列表 (Web ACL)），这些规则根据您定义的可自定义 Web 安全规则和条件允许、阻止或监控（统计）Web 请求。将 AWS AppSync API 与集成后 AWS WAF，您可以更好地控制和了解您的 API 接受的 HTTP 流量。要了解更多信息 AWS WAF，请参阅《 AWS WAF 开发者指南》中的[AWS WAF 工作原理](https://docs.aws.amazon.com/waf/latest/developerguide/how-aws-waf-works.html)。

您可以使用 AWS WAF 保护您的 AppSync API 免受常见 Web 漏洞的侵害，例如 SQL 注入和跨站脚本 (XSS) 攻击。这些威胁可能会影响 API 的可用性和性能、损害安全性或消耗过多的资源。例如，您可以创建规则以允许或阻止以下请求：来自指定 IP 地址范围的请求；来自 CIDR 块的请求；源自特定国家/地区或区域的请求；包含恶意 SQL 代码的请求；或者包含恶意脚本的请求。

您还可以创建与 HTTP 标头、方法、查询字符串、URI 和请求正文中的指定字符串或正则表达式模式匹配的规则（限制为前 8 KB）。此外，您可以创建规则来阻止来自特定用户代理、恶意机器人和内容抓取程序的攻击。例如，您可以使用基于速率的规则来指定每个客户端 IP 在尾随的、不断更新的 5 分钟期间内允许的 Web 请求数。

要详细了解支持的规则类型和其他 AWS WAF 功能，请参阅[AWS WAF 开发者指南](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html)和 [AWS WAF API 参考](https://docs.aws.amazon.com/waf/latest/APIReference/API_Types_AWS_WAFV2.html)。

**重要**  
AWS WAF 是您抵御网络漏洞的第一道防线。 AWS WAF 在 API 上启用后，将在其他访问控制功能（例如 API 密钥授权、IAM 策略、OIDC 令牌和 Amazon Cognito 用户池）之前评估 AWS WAF 规则。

## 将 AppSync API 与集成 AWS WAF
<a name="integrate-API-with-WAF"></a>

您可以 AWS WAF 使用 AWS 管理控制台、、或任何其他兼容的客户端将 Appsync API 与集成。 AWS CLI AWS CloudFormation

**将 AWS AppSync API 与集成 AWS WAF**

1. 创建 AWS WAF Web ACL。有关使用 [AWS WAF 控制台](https://console.aws.amazon.com/waf/)的详细步骤，请参阅 [Creating a web ACL](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-creating.html)。

1. 定义 Web ACL 的规则。在创建 Web ACL 的过程中定义了一个或多个规则。有关如何构建规则的信息，请参阅 [AWS WAF rules](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rules.html)。有关您可以为 AWS AppSync API 定义的有用规则的示例，请参阅[为 Web ACL 创建规则](#Creating-web-acl-rules)。

1. 将 Web ACL 与 AWS AppSync API 相关联。[您可以在[AWS WAF 控制台或控制台](https://console.aws.amazon.com/wafv2/)中执行AppSync 此步骤。](https://console.aws.amazon.com/appsync/)
   + 要在 AWS WAF 控制台中将 Web ACL 与某 AWS AppSync 个 API 关联，请按照《开发者指南》中有关将 [Web ACL 与 AWS 资源关联或取消关联的](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-associating-aws-resource.html)说明进行操作。 AWS WAF 
   + 在 AWS AppSync 控制台中将 Web ACL 与某 AWS AppSync 个 API 关联

     1. 登录 AWS 管理控制台 并打开[AppSync 控制台](https://console.aws.amazon.com/appsync/)。

     1. 选择您希望与 Web ACL 关联的 API。

     1. 在导航窗格中，选择 **Settings（设置）**。

     1. 在 **Web 应用程序防火墙**部分中，开启**启用 AWS WAF**。

     1. 在 **Web ACL** 下拉列表中，选择要与您的 API 关联的 Web ACL 名称。

     1. 选择**保存**以将该 Web ACL 与您的 API 关联。

   

**注意**  
在 AWS WAF 控制台中创建 Web ACL 后，可能需要几分钟时间才能使用新的 Web ACL。如果您在 **Web 应用程序防火墙**菜单中没有看到新创建的 Web ACL，请等待几分钟，然后重试将 Web ACL 与您的 API 关联的步骤。

**注意**  
AWS WAF 集成仅支持实时端点`Subscription registration message`的事件。 AWS AppSync 将以错误消息而不是任何`Subscription registration message`被屏蔽的`start_ack`消息进行响应 AWS WAF。

将网页 ACL 与 AWS AppSync API 关联后，您将使用管理网页 ACL AWS WAF APIs。除非您想将网页 ACL 与您的 AWS AppSync API 重新关联，否则您无需将该 AWS AppSync API 与其他 Web ACL 关联。

## 为 Web ACL 创建规则
<a name="Creating-web-acl-rules"></a>

规则定义如何检查 Web 请求以及在 Web 请求符合检查条件时执行的操作。规则本身并不存在。 AWS WAF 您可以在规则组或定义规则的 Web ACL 中按名称访问规则。有关更多信息，请参阅 [AWS WAF rules](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rules.html)。以下示例演示如何定义和关联对保护 AppSync API 有用的规则。

**Example 用于限制请求正文大小的 Web ACL 规则**  
以下是限制请求正文大小的规则示例。在 AWS WAF 控制台中创建 Web ACL 时，这将在**规则 JSON 编辑器**中输入。  

```
{
    "Name": "BodySizeRule", 
    "Priority": 1, 
    "Action": {
        "Block": {}
    }, 
    "Statement": {
        "SizeConstraintStatement": {
            "ComparisonOperator": "GE",
            "FieldToMatch": {
                "Body": {}
            },
            "Size": 1024, 
            "TextTransformations": [
                {
                    "Priority": 0, 
                    "Type": "NONE"
                }
             ]
          }
       }, 
       "VisibilityConfig": {
           "CloudWatchMetricsEnabled": true, 
           "MetricName": "BodySizeRule", 
           "SampledRequestsEnabled": true
        }
}
```
使用上述示例规则创建 Web ACL 后，必须将其与您的 AppSync API 关联。除了使用之外 AWS 管理控制台，您还可以在中运行以下命令 AWS CLI 来执行此步骤。  

```
aws waf associate-web-acl --web-acl-id waf-web-acl-arn --resource-arn appsync-api-arn
```
可能需要几分钟才能传播更改，但在运行该命令后，包含的正文大于 1024 字节的请求将被 AWS AppSync拒绝。  
在 AWS WAF 控制台中创建新的 Web ACL 后，可能需要几分钟才能将该 Web ACL 与 API 关联。如果您运行 CLI 命令并出现 `WAFUnavailableEntityException` 错误，请等待几分钟，然后再次尝试运行该命令。

**Example 限制来自单个 IP 地址的请求的 Web ACL 规则**  
以下是将一个 AppSync API 限制为来自单个 IP 地址的 100 个请求的规则示例。在 AWS WAF 控制台中使用基于速率的**规则创建 Web ACL 时，这将在规则 JSON 编辑器**中输入。  

```
{
  "Name": "Throttle",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "Throttle"
  },
  "Statement": {
    "RateBasedStatement": {
      "Limit": 100,
      "AggregateKeyType": "IP"
    }
  }
}
```
使用上述示例规则创建 Web ACL 后，必须将其与您的 AppSync API 关联。您可以 AWS CLI 通过运行以下命令在中执行此步骤。  

```
aws waf associate-web-acl --web-acl-id waf-web-acl-arn --resource-arn appsync-api-arn
```

**Example 禁止对 API 运行 GraphQL \$1\$1schema 自省查询的 Web ACL 规则**  
以下是一个禁止对 API 运行 GraphQL \$1\$1schema 自省查询的规则示例。将阻止任何包含字符串“\$1\$1schema”的 HTTP 正文。在 AWS WAF 控制台中创建 Web ACL 时，这将在**规则 JSON 编辑器**中输入。  

```
{
  "Name": "BodyRule",
  "Priority": 5,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "BodyRule"
  },
  "Statement": {
    "ByteMatchStatement": {
      "FieldToMatch": {
        "Body": {}
      },
      "PositionalConstraint": "CONTAINS",
      "SearchString": "__schema",
      "TextTransformations": [
        {
          "Type": "NONE",
          "Priority": 0
        }
      ]
    }
  }
}
```
使用上述示例规则创建 Web ACL 后，必须将其与您的 AppSync API 关联。您可以 AWS CLI 通过运行以下命令在中执行此步骤。  

```
aws waf associate-web-acl --web-acl-id waf-web-acl-arn --resource-arn appsync-api-arn
```