

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

# 在 API Gateway 中控制和管理對 WebSocket API 的存取
<a name="apigateway-websocket-api-control-access"></a>

API Gateway 支援多種機制來控制和管理對 WebSocket API 的存取。

您可以使用下列機制進行身分驗證和授權：
+ **標準 AWS IAM 角色和政策**提供靈活且強大的存取控制。您可以使用 IAM 角色和原則來控制誰可以建立和管理您的 API，以及誰可以叫用它們。如需更多詳細資訊，請參閱 [使用 IAM 授權控制對 WebSocket API 的存取](apigateway-websocket-control-access-iam.md)。
+ **IAM 標籤**可搭配 IAM 政策一起用來控制存取。如需更多詳細資訊，請參閱 [使用標籤來控制對 API Gateway REST API 資源的存取](apigateway-tagging-iam-policy.md)。
+ **Lambda 授權方** 是控制 API 存取權的 Lambda 函數。如需詳細資訊，請參閱[使用 AWS Lambda REQUEST 授權方控制對 WebSocket APIs存取](apigateway-websocket-api-lambda-auth.md)。

為了改善您的安全狀態，建議您為所有 WebSocket API 上的 `$connect` 路由設定授權方。您可能需要這樣做，才能符合各種合規架構。如需詳細資訊，請參閱《AWS Security Hub 使用者指南》**中的 [Amazon API Gateway 控制項](https://docs.aws.amazon.com/securityhub/latest/userguide/apigateway-controls.html)。

**Topics**
+ [使用 IAM 授權控制對 WebSocket API 的存取](apigateway-websocket-control-access-iam.md)
+ [使用 AWS Lambda REQUEST 授權方控制對 WebSocket APIs存取](apigateway-websocket-api-lambda-auth.md)

# 使用 IAM 授權控制對 WebSocket API 的存取
<a name="apigateway-websocket-control-access-iam"></a>

WebSocket API 及 [REST API](api-gateway-control-access-using-iam-policies-to-invoke-api.md) 具備類似的 IAM 授權，除了以下例外：
+ 除了現有動作 (`execute-api`、`ManageConnections`)，`Invoke` 動作也支援 `InvalidateCache`。`ManageConnections` 會控制 @connections API 的存取。
+ WebSocket 路由使用不同的 ARN 格式：

  ```
  arn:aws:execute-api:region:account-id:api-id/stage-name/route-key
  ```
+ `@connections` API 使用的 ARN 格式與 REST API 相同：

  ```
  arn:aws:execute-api:region:account-id:api-id/stage-name/POST/@connections
  ```

**重要**  
使用 [IAM 授權](#apigateway-websocket-control-access-iam)時，您必須使用[第 4 版簽署程序 (SigV4)](https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html) 來簽署請求。

例如，您可針對用戶端設定下列政策。除了 `Invoke` 階段內的秘密路由，此範例允許所有人針對所有路由傳送訊息 (`prod`)，並防止所有人在任何階段回傳訊息給已連線的用戶端 (`ManageConnections`)。

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "execute-api:Invoke"
            ],
            "Resource": [
                "arn:aws:execute-api:us-east-1:111122223333:api-id/prod/*"
            ]
        },
        {
            "Effect": "Deny",
            "Action": [
                "execute-api:Invoke"
            ],
            "Resource": [
                "arn:aws:execute-api:us-east-1:111122223333:api-id/prod/secret"
            ]
        },
        {
            "Effect": "Deny",
            "Action": [
                "execute-api:ManageConnections"
            ],
            "Resource": [
                "arn:aws:execute-api:us-east-1:111122223333:api-id/*"
            ]
        }
    ]
}
```

------

# 使用 AWS Lambda REQUEST 授權方控制對 WebSocket APIs存取
<a name="apigateway-websocket-api-lambda-auth"></a>

WebSocket API 及 [REST API](apigateway-use-lambda-authorizer.md#api-gateway-lambda-authorizer-lambda-function-create) 具備類似的 Lambda 授權方函數，除了以下例外：
+  您只能使用 `$connect` 路由的 Lambda 授權方函數。
+ 您不能使用路徑變數 (`event.pathParameters`)，因為路徑已固定。
+ `event.methodArn` 與同等的 REST API 不同，因其沒有 HTTP 方法。若是 `$connect`，`methodArn` 會以 `"$connect"` 做為結尾：

  ```
  arn:aws:execute-api:region:account-id:api-id/stage-name/$connect
  ```
+ `event.requestContext` 內的內容變數與 REST API 的不同。

 下列範例顯示對 WebSocket API 的 `REQUEST` 授權方的輸入：

```
{
    "type": "REQUEST",
    "methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/default/$connect",
    "headers": {
        "Connection": "upgrade",
        "content-length": "0",
        "HeaderAuth1": "headerValue1",
        "Host": "abcdef123.execute-api.us-east-1.amazonaws.com",
        "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits",
        "Sec-WebSocket-Key": "...",
        "Sec-WebSocket-Version": "13",
        "Upgrade": "websocket",
        "X-Amzn-Trace-Id": "...",
        "X-Forwarded-For": "...",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "multiValueHeaders": {
        "Connection": [
            "upgrade"
        ],
        "content-length": [
            "0"
        ],
        "HeaderAuth1": [
            "headerValue1"
        ],
        "Host": [
            "abcdef123.execute-api.us-east-1.amazonaws.com"
        ],
        "Sec-WebSocket-Extensions": [
            "permessage-deflate; client_max_window_bits"
        ],
        "Sec-WebSocket-Key": [
            "..."
        ],
        "Sec-WebSocket-Version": [
            "13"
        ],
        "Upgrade": [
            "websocket"
        ],
        "X-Amzn-Trace-Id": [
            "..."
        ],
        "X-Forwarded-For": [
            "..."
        ],
        "X-Forwarded-Port": [
            "443"
        ],
        "X-Forwarded-Proto": [
            "https"
        ]
    },
    "queryStringParameters": {
        "QueryString1": "queryValue1"
    },
    "multiValueQueryStringParameters": {
        "QueryString1": [
            "queryValue1"
        ]
    },
    "stageVariables": {},
    "requestContext": {
        "routeKey": "$connect",
        "eventType": "CONNECT",
        "extendedRequestId": "...",
        "requestTime": "19/Jan/2023:21:13:26 +0000",
        "messageDirection": "IN",
        "stage": "default",
        "connectedAt": 1674162806344,
        "requestTimeEpoch": 1674162806345,
        "identity": {
            "sourceIp": "..."
        },
        "requestId": "...",
        "domainName": "abcdef123.execute-api.us-east-1.amazonaws.com",
        "connectionId": "...",
        "apiId": "abcdef123"
    }
}
```

下列範例 Lambda 授權方函數為 [Lambda 授權方函數的其他範例](apigateway-use-lambda-authorizer.md#api-gateway-lambda-authorizer-lambda-function-create) 內 REST API 之 Lambda 授權方函數的 WebSocket 版本：

------
#### [ Node.js ]

```
   // A simple REQUEST authorizer example to demonstrate how to use request 
   // parameters to allow or deny a request. In this example, a request is  
   // authorized if the client-supplied HeaderAuth1 header and QueryString1 query parameter
   // in the request context match the specified values of
   // of 'headerValue1' and 'queryValue1' respectively.
            export const handler = function(event, context, callback) {
    console.log('Received event:', JSON.stringify(event, null, 2));

   // Retrieve request parameters from the Lambda function input:
   var headers = event.headers;
   var queryStringParameters = event.queryStringParameters;
   var stageVariables = event.stageVariables;
   var requestContext = event.requestContext;
       
   // Parse the input for the parameter values
   var tmp = event.methodArn.split(':');
   var apiGatewayArnTmp = tmp[5].split('/');
   var awsAccountId = tmp[4];
   var region = tmp[3];
   var ApiId = apiGatewayArnTmp[0];
   var stage = apiGatewayArnTmp[1];
   var route = apiGatewayArnTmp[2];
       
   // Perform authorization to return the Allow policy for correct parameters and 
   // the 'Unauthorized' error, otherwise.
   var authResponse = {};
   var condition = {};
    condition.IpAddress = {};
    
   if (headers.HeaderAuth1 === "headerValue1"
       && queryStringParameters.QueryString1 === "queryValue1") {
        callback(null, generateAllow('me', event.methodArn));
    }  else {
        callback(null, generateDeny('me', event.methodArn)); 
    }
}
    
// Helper function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
   // Required output:
   var authResponse = {};
    authResponse.principalId = principalId;
   if (effect && resource) {
       var policyDocument = {};
        policyDocument.Version = '2012-10-17		 	 	 '; // default version
       policyDocument.Statement = [];
       var statementOne = {};
        statementOne.Action = 'execute-api:Invoke'; // default action
       statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement[0] = statementOne;
        authResponse.policyDocument = policyDocument;
    }
   // Optional output with custom properties of the String, Number or Boolean type.
   authResponse.context = {
       "stringKey": "stringval",
       "numberKey": 123,
       "booleanKey": true
    };
   return authResponse;
}
    
var generateAllow = function(principalId, resource) {
   return generatePolicy(principalId, 'Allow', resource);
}
    
var generateDeny = function(principalId, resource) {
   return generatePolicy(principalId, 'Deny', resource);
}
```

------
#### [ Python ]

```
# A simple REQUEST authorizer example to demonstrate how to use request
# parameters to allow or deny a request. In this example, a request is
# authorized if the client-supplied HeaderAuth1 header and QueryString1 query parameter
# in the request context match the specified values of
# of 'headerValue1' and 'queryValue1' respectively.

import json


def lambda_handler(event, context):
    print(event)

    # Retrieve request parameters from the Lambda function input:
    headers = event['headers']
    queryStringParameters = event['queryStringParameters']
    stageVariables = event['stageVariables']
    requestContext = event['requestContext']

    # Parse the input for the parameter values
    tmp = event['methodArn'].split(':')
    apiGatewayArnTmp = tmp[5].split('/')
    awsAccountId = tmp[4]
    region = tmp[3]
    ApiId = apiGatewayArnTmp[0]
    stage = apiGatewayArnTmp[1]
    route = apiGatewayArnTmp[2]

    # Perform authorization to return the Allow policy for correct parameters
    # and the 'Unauthorized' error, otherwise.

    authResponse = {}
    condition = {}
    condition['IpAddress'] = {}

    if (headers['HeaderAuth1'] ==
            "headerValue1" and queryStringParameters["QueryString1"] == "queryValue1"):
        response = generateAllow('me', event['methodArn'])
        print('authorized')
        return json.loads(response)
    else:
        response = generateDeny('me', event['methodArn'])
        print('unauthorized')
        return json.loads(response)

    # Help function to generate IAM policy


def generatePolicy(principalId, effect, resource):
    authResponse = {}
    authResponse['principalId'] = principalId
    if (effect and resource):
        policyDocument = {}
        policyDocument['Version'] = '2012-10-17		 	 	 '
        policyDocument['Statement'] = []
        statementOne = {}
        statementOne['Action'] = 'execute-api:Invoke'
        statementOne['Effect'] = effect
        statementOne['Resource'] = resource
        policyDocument['Statement'] = [statementOne]
        authResponse['policyDocument'] = policyDocument

    authResponse['context'] = {
        "stringKey": "stringval",
        "numberKey": 123,
        "booleanKey": True
    }

    authResponse_JSON = json.dumps(authResponse)

    return authResponse_JSON


def generateAllow(principalId, resource):
    return generatePolicy(principalId, 'Allow', resource)


def generateDeny(principalId, resource):
    return generatePolicy(principalId, 'Deny', resource)
```

------

若要將前述 Lambda 函數設定為 WebSocket API 的 `REQUEST` 授權方函數，請遵循 [REST API](configure-api-gateway-lambda-authorization.md#configure-api-gateway-lambda-authorization-with-console) 內的相同程序。

若要將 `$connect` 路由在主控台內設定為使用此 Lambda 授權方，請選取或建立 `$connect` 路由。在**路由請求設定**區段中，選擇**編輯**。在**授權**下拉式選單中選取您的授權方，然後選擇**儲存變更**。

欲測試授權方，必須建立新的連線。變更 `$connect` 內的授權方不會影響已經連線的用戶端。連線至您的 WebSocket API 時，必須提供已設定身分來源的值。例如，您可傳送有效查詢字串，標頭使用 `wscat`，藉此進行連線，如下列範例所示：

```
wscat -c 'wss://myapi.execute-api.us-east-1.amazonaws.com/beta?QueryString1=queryValue1' -H HeaderAuth1:headerValue1
```

若您嘗試連線但不具備有效的身分值，將收到 `401` 回應：

```
wscat -c wss://myapi.execute-api.us-east-1.amazonaws.com/beta
error: Unexpected server response: 401
```