

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 設定授權和身分驗證來保護 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 互動。您可以在 your 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`為主要或預設授權類型時，您無法將其再次指定為其他授權模式之一。同樣地，您無法在其他`AWS_IAM`授權模式中複製 `API_KEY``AWS_LAMBDA`或 。您可以使用多個 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>

未驗證的 API 相較於已驗證 API 需要更嚴格的調節。其中一種控制未授權 GraphQL 端點調節的方法就是使用 API 金鑰。API 金鑰是應用程式中的硬式編碼值，會在您建立未經驗證的 GraphQL 端點時，由 AWS AppSync 服務產生。您可以從主控台、CLI 或 API [AWS AppSync 參考輪換 API](https://docs.aws.amazon.com/appsync/latest/APIReference/) 金鑰。

------
#### [ 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. 如果您尚未這麼做，請設定您對 CLI AWS 的存取。如需詳細資訊，請參閱[組態基本概念](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 函數需要`appsync.amazonaws.com`套用 的主體政策，才能允許 AWS AppSync 呼叫它們。此動作會在 AWS AppSync 主控台中自動完成； AWS AppSync 主控台*不會*移除政策。如需將政策連接至 Lambda 函數的詳細資訊，請參閱《 AWS Lambda 開發人員指南》中的以[資源為基礎的政策](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html#permissions-resource-serviceinvoke)。

您指定的 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 授權函數傳回的下列金鑰：

**注意**  
WebSocket 連線操作 `operationName`中 `requestContext` 的值由 設定為 AWS AppSync "`DeepDish:Connect`"。

### 函數清單
<a name="aws-lambda-authorization-list"></a>

`isAuthorized` （布林值，必要）  
布林值，指出 中的值是否`authorizationToken`獲授權對 GraphQL API 進行呼叫。  
如果此值為 true，GraphQL API 的執行會繼續。如果此值為 false，`UnauthorizedException`則會引發

`deniedFields` （字串清單，選用）  
即使從解析程式傳回值`null`， 也會強制變更為 的清單。  
每個項目都是完整格式的欄位 ARN，格式為 `arn:aws:appsync:us-east-1:111122223333:apis/GraphQLApiId/types/TypeName/fields/FieldName`或簡短格式的 `TypeName.FieldName`。當兩個 APIs共用 Lambda 函數授權方，且兩個 APIs 之間的常見類型和欄位之間可能會有模棱兩可的情況時，應使用完整的 ARN 表單。

`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`arn:aws:appsync:region:accountId:apis/GraphQLApiId/types/typeName/fields/fieldName`。

若要在 中新增 Lambda 函數作為預設授權模式 AWS AppSync：

------
#### [ 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`和 授權模式時，使用 SigV4 簽章做為 Lambda `AWS_LAMBDA`授權字符，請執行下列動作：
+ 若要建立新的 Lambda 授權字符，請將隨機尾碼和/或字首新增至 SigV4 簽章。
+ 若要擷取原始 SigV4 簽章，請從 Lambda 授權字符中移除隨機字首和/或尾碼，以更新您的 Lambda 函數。然後，使用原始 SigV4 簽章進行身分驗證。

如果您想要在為 API 啟用授權模式或 `AMAZON_COGNITO_USER_POOLS`和 `AWS_LAMBDA` 授權模式時，使用 OIDC 權杖做為 Lambda AWS AppSync`OPENID_CONNECT`授權權杖，請執行下列動作：
+ 若要建立新的 Lambda 授權字符，請將隨機尾碼和/或字首新增至 OIDC 字符。Lambda 授權字符不應包含承載配置字首。
+ 若要擷取原始 OIDC 權杖，請從 Lambda 授權權杖中移除隨機字首和/或尾碼，以更新您的 Lambda 函數。然後，使用原始 OIDC 字符進行身分驗證。

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

此授權類型會在 [AWS GraphQL API 上強制執行簽章第 4 版簽署程序](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)。 GraphQL 您可以將身分與存取管理 ([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 主控台的主要 API 列表頁面中找到 `YourGraphQLApiId`，就在您的 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 連線](https://openid.net/specs/openid-connect-core-1_0.html) (OIDC) 權杖。您的應用程式可以運用由 OIDC 提供者定義的使用者與權限來控制存取權限。

發行者 URL 是您提供給 to AWS AppSync 的唯一必要組態值 （例如 `https://auth.example.com`)。此 URL 必須透過 HTTPS 定址。 AWS AppSync 會附加`/.well-known/openid-configuration`到發行者 URL，並根據 OpenID Connect Discovery `https://auth.example.com/.well-known/openid-configuration` 規格在 找到 OpenID 組態。 [OpenID ](https://openid.net/specs/openid-connect-discovery-1_0.html) 它預期會在此 URL 擷取相容於 [RFC5785](https://tools.ietf.org/html/rfc5785) 的 JSON 文件。此 JSON 文件必須包含`jwks_uri`金鑰，該金鑰指向具有簽署金鑰的 JSON Web Key Set (JWKS) 文件。 AWS AppSync 要求 JWKS 包含 `kty`和 的 JSON 欄位`kid`。

AWS AppSync 支援各種簽署演算法。


| 簽署演算法 | 
| --- | 
| RS256 | 
| RS384 | 
| RS512 | 
| PS256 | 
| PS384 | 
| PS512 | 
| HS256 | 
| HS384 | 
| HS512 | 
| ES256 | 
| ES384 | 
| ES512 | 

我們建議您使用 RSA 演算法。由提供者發行的字符必須包含字符發行的時間 (`iat`)，且可包含驗證時的時間 (`auth_time`)。您可以提供 OpenID Connect 組態中的發行時間的 TTL 值 (`iatTTL`) 和身分驗證時間 (`authTTL`)，以供額外驗證。如果您的提供者授權多個應用程式，您也可以提供用於根據用戶端 ID 授權的一般表達式 (`clientId`)。當 OpenID Connect 組態中有 `clientId` 時， AWS AppSync 會要求 與字符中的 `aud`或 宣告相符`clientId`，以驗證`azp`宣告。

若要驗證多個用戶端 IDs請使用管道運算子 (「\$1」)，這是規則表達式中的「或」。例如，如果您的 OIDC 應用程式有四個用戶端具有用戶端 IDs，例如 0A1S2D, 1F4G9H, 1J6L4B, 6GS5MG，只要驗證前三個用戶端 IDs，您會在用戶端 ID 欄位中放置 1F4G9H\$11J6L4B\$16GS5MG。

如果 API 設定了多種授權類型， AWS AppSync 會將其與 API 組態中指定的發行者 URL 進行比較，以驗證來自請求標頭的 JWT 字符中存在的發行者 (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 使用者集區 - 部落客和讀者 - 中有兩個群組，而且您想要限制讀者，使其無法新增項目，則結構描述應如下所示：

```
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"])
}
...
```

請注意，如果您想要將存取權限預設為特定授予或拒絕授予策略，您可以省略 `@aws_auth` 指令。在透過主控台或以下 CLI 命令建立 GraphQL API 時，您可以在使用者集區組態中指定授予或拒絕授予策略：

```
$ 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_tw/appsync/latest/devguide/images/FGAC.png)


若要新增此功能，請新增 `editPost` 的 GraphQL 欄位，如下所示：

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

即使呼叫者不是建立文章的作者，請求處理常式也會擷取項目。為了防止傳回所有資料，回應處理常式會檢查以確保呼叫者符合項目的作者。如果發起人不符合此檢查，只會傳回 nul在某些情況下，您無法控制數據源的響應，但又不想在成功寫入或讀取數據源時將不必要的信息發送給客戶端。l 回應。

## 資料來源存取
<a name="data-source-access"></a>

AWS AppSync 會使用 Identity and Access Management ([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 的各種授權模式，其中也簡介精細分級授權機制，以了解各項概念與流程。Since AWS AppSync 可讓您透過使用 GraphQL 解析程式[映射範本](resolver-mapping-template-reference-overview.md#aws-appsync-resolver-mapping-template-reference-overview)對資料執行邏輯完整操作，您可以使用使用者身分、條件和資料注入的組合，以非常靈活的方式保護讀取或寫入時的資料。

如果您不熟悉 editing 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)，您可以在解析程式中使用。這表示您可依據解析程式邏輯，將權限適當地授與寫入或讀取操作。如果此邏輯位於資源層級，例如只有特定具名使用者或群組可以讀取/寫入特定資料庫資料列，則必須儲存該「授權中繼資料」。 AWS AppSync 不會儲存任何資料，因此您必須將此授權中繼資料與資源一起存放，才能計算許可。授權中繼資料通常是 DynamoDB 表之中的屬性 (欄)，例如**擁有者**或使用者/群組清單。例如其中可能有**讀取器**及**寫入器**屬性。

大致而言，這表示若您由資料來源讀取個別項目，就會在解析程式由資料來源讀取之後，於回應範本執行條件式 `#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_tw/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 查詢傳回清單而言，效能較高的作法是在要求範本執行檢查，並僅於滿足授權條件時傳回資料。實作之後將：

1. GetItem - 個別記錄的授權檢查。使用 `#if() ... #end` 陳述式完成。

1. 掃描/查詢操作 - 授權檢查為 `"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
```

這裡提出兩點注意事項，在剩餘章節中會用到。首先，如果使用 Amazon Cognito 使用者集區，檢查會使用`$context.identity.username`易記的使用者註冊名稱，如果使用 IAM，檢查將是使用者身分 （包括 Amazon Cognito 聯合身分）。還有其他值要存放給擁有者，例如唯一的「Amazon Cognito 身分」值，在聯合來自多個位置的登入時很有用，您應該檢閱[解析程式映射範本內容參考](resolver-context-reference.md#aws-appsync-resolver-mapping-template-context-reference)中可用的選項。

第二，以 `$util.unauthorized()` 進行的條件式否則檢查回應為完全可選用的選項，但建議做為設計 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>

另一種熱門的授權選擇，就是允許一群人讀取資料。在以下範例中，`"filter":{"expression":...}` 僅傳回表格掃描的值 (若使用者執行的 GraphQL 查詢列於 `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()"` 操作的使用方式類似，不過這是所有群組的邏輯 OR，使用者可能是其中的一部分，需要在設定成員資格時加以考量。在此案例中，我們針對使用者所屬的各個群組建立以下 `$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>

您也可以使用條件式篩選器，選擇將資料標示為公開、私人或其他布林檢查。並於之後合併為回應範本之中授權篩選的一部分。此項檢查適合用於暫時隱藏資料，或由檢視之中移除資料，不必嘗試控制群組成員資格。

例如，假設您在名為 `public` 的 DynamoDB 表格，以 `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
```

以上程式碼也可使用邏輯 OR (`||`)，在使用者擁有權限或記錄為公開的情況下能夠讀取記錄：

```
#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()` 欄位，然後選取 **Messages (訊息)** 資料表資料來源：

```
{
    "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`. 這會傳回 AWS Identity and Access Management 或 Amazon Cognito 使用者的使用者資訊。回應範本為簡單的 `$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
```

在此情況下，您會進行三項授權檢查。第一項檢查確保結果已返回。第二項檢查確保使用者並未訂閱其他人的訊息。第三個會檢查`isAuthorizedForSubscriptions`存放為 的 DynamoDB 屬性，以確保使用者能夠訂閱任何欄位`BOOL`。

若要測試物件，您可以使用 Amazon Cognito 使用者集區和名為「Nadia」的使用者登入 AWS AppSync 主控台，然後執行下列 GraphQL 訂閱：

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

如果在**許可**資料表之中，存在 `Nadia` 的 `username` 索引鍵屬性記錄，且 `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 Injection 和跨網站指令碼 (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 是您防範 Web 入侵的第一道防線。在 API 上啟用 AWS WAF 時，會在其他存取控制功能之前評估 AWS WAF 規則，例如 API 金鑰授權、IAM 政策、OIDC 權杖和 Amazon Cognito 使用者集區。

## 將 AppSync API 與 整合 AWS WAF
<a name="integrate-API-with-WAF"></a>

您可以使用 AWS CLI AWS CloudFormation、 或任何其他相容的用戶端，將 Appsync API 與 AWS WAF AWS 管理主控台整合。

**將 AWS AppSync API 與 整合 AWS WAF**

1. 建立 AWS WAF Web ACL。如需使用[AWS WAF 主控台](https://console.aws.amazon.com/waf/)的詳細步驟，請參閱[建立 Web ACL](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-creating.html)。

1. 定義 Web ACL 的規則。規則會在建立 Web ACL 的過程中定義。如需如何建構規則的資訊，請參閱 [AWS WAF 規則](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/)中執行此步驟。
   + 若要將 Web ACL 與 AWS WAF 主控台中的 AWS AppSync API 建立關聯，請遵循《 AWS WAF 開發人員指南》中[建立或取消 Web ACL 與 AWS 資源的關聯](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-associating-aws-resource.html)的指示。
   + 在主控台中 AWS AppSync 將 Web ACL 與 AWS AppSync API 建立關聯

     1. 登入 AWS 管理主控台 並開啟 [AppSync 主控台](https://console.aws.amazon.com/appsync/)。

     1. 選擇您要與 Web ACL 建立關聯的 API。

     1. 在導覽窗格中，選擇**設定**。

     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。

將 Web ACL 與 AWS AppSync API 建立關聯後，您將使用 AWS WAF APIs管理 Web ACL。除非您想要將 AWS AppSync API 與不同的 Web ACL 建立關聯，否則您不需要重新建立 Web ACL 與 AWS AppSync API 的關聯。

## 建立 Web ACL 的規則
<a name="Creating-web-acl-rules"></a>

規則定義如何檢查 Web 請求，以及當 Web 請求符合檢查條件時該怎麼做。規則本身不存在於 中 AWS WAF 。您可以在規則群組中或定義規則的 Web ACL 中，依名稱存取規則。如需詳細資訊，請參閱 [AWS WAF 規則](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 Web ACL 規則，以防止 GraphQL \$1\$1結構描述對 API 的自我檢查查詢**  
以下是防止 GraphQL \$1\$1schema 向 API 進行自我檢查的規則範例。任何包含字串 "\$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
```