返回文章列表

Serverless API CORS 與 Cognito 操作實踐

本文探討 Serverless 架構下 RESTful API 的 CORS 設定,說明如何使用 AWS CLI 和 CloudFormation 實作,並提供 JavaScript SDK 操作 Cognito 的程式碼範例,涵蓋使用者註冊、驗證和登入流程,方便前端工程師整合 Cognito 服務。

Web 開發 Serverless

在 Serverless 架構中,API Gateway 常作為前端應用程式的入口,因此正確設定 CORS 至關重要。本文除了 AWS CLI 的操作步驟,也提供 CloudFormation 的組態方式,讓 CORS 設定更簡潔易於管理。此外,現代 Web 應用程式多仰賴前端 JavaScript 與後端服務互動,因此文章也詳細說明如何使用 JavaScript SDK 與 Cognito 進行使用者管理,包含註冊、驗證和登入等關鍵流程,並提供可直接在 CodePen 測試的程式碼範例,有助於快速上手 Cognito 的前端整合。

實作 CORS 的 Serverless API

在開發 Serverless 架構的 RESTful API 時,CORS(Cross-Origin Resource Sharing)是一個重要的議題。CORS 允許網頁從不同的網域請求資源,這對於現代網頁應用非常重要。本篇文章將介紹如何使用 AWS CLI 和 CloudFormation 設定 CORS。

使用 AWS CLI 設定 CORS

要使用 AWS CLI 設定 CORS,需要執行以下步驟:

  1. 建立 API:首先,建立一個新的 API Gateway。

    aws apigateway create-rest-api --name 'My API'
    
  2. 取得根資源 ID:取得根資源的 ID。

    aws apigateway get-resources --rest-api-id xenqybowjg
    
  3. 建立資源和子資源:建立一個新的資源和子資源。

    aws apigateway create-resource --rest-api-id xenqybowjg --parent-id sfgfk6 --path-part greeting
    aws apigateway create-resource --rest-api-id xenqybowjg --parent-id sfgfk6 --path-part '{name}'
    
  4. 設定 GET 方法

    • 建立 GET 方法
      aws apigateway put-method --rest-api-id xenqybowjg --resource-id sfgfk6 --http-method GET --authorization 'NONE'
      
    • 設定 GET 方法回應
      aws apigateway put-method-response --rest-api-id xenqybowjg --resource-id sfgfk6 --http-method GET --status-code 200 --response-parameters "method.response.header.Access-Control-Allow-Origin=true"
      
    • 設定整合請求
      aws apigateway put-integration --rest-api-id xenqybowjg --resource-id sfgfk6 --http-method GET --type MOCK --integration-http-method GET --request-templates "{\"application/json\":\"{\"statusCode\": \"200\"}\"}"
      
    • 設定整合回應
      aws apigateway put-integration-response --rest-api-id xenqybowjg --resource-id sfgfk6 --http-method GET --status-code 200 --response-parameters "method.response.header.Access-Control-Allow-Origin='*'"
      
  5. 設定 OPTIONS 方法(用於 CORS 預檢請求):

    • 建立 OPTIONS 方法
      aws apigateway put-method --rest-api-id xenqybowjg --resource-id sfgfk6 --http-method OPTIONS --authorization 'NONE'
      
    • 設定 OPTIONS 方法回應
      aws apigateway put-method-response --rest-api-id xenqybowjg --resource-id sfgfk6 --http-method OPTIONS --status-code 200 --response-parameters "method.response.header.Access-Control-Allow-Origin=true,method.response.header.Access-Control-Allow-Headers=true"
      
    • 設定整合請求
      aws apigateway put-integration --rest-api-id xenqybowjg --resource-id sfgfk6 --http-method OPTIONS --type MOCK --integration-http-method OPTIONS --request-templates "{\"application/json\":\"{\"statusCode\": \"200\"}\"}"
      
    • 設定整合回應,使用 put-method-integration-response-options.json 檔案:
      {
        "method.response.header.Access-Control-Allow-Origin": "'*'",
        "method.response.header.Access-Control-Allow-Headers": "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
      }
      
      aws apigateway put-integration-response --rest-api-id xenqybowjg --resource-id sfgfk6 --http-method OPTIONS --status-code 200 --response-parameters file://put-method-integration-response-options.json
      
  6. 佈署 API

    aws apigateway create-deployment --rest-api-id xenqybowjg --stage-name dev
    

使用 CloudFormation 設定 CORS

CloudFormation 提供了一種更簡潔的方式來定義和佈署 AWS 資源。你可以使用 AWS::ApiGateway::Method 資源型別來定義 CORS 設定。

程式碼範例與解析

Resources:
  MyApi:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
      Name: !Sub 'my-api-${AWS::Region}'

  MyResource:
    Type: 'AWS::ApiGateway::Resource'
    Properties:
      RestApiId: !Ref MyApi
      ParentId: !GetAtt MyApi.RootResourceId
      PathPart: 'greeting'

  MyMethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      RestApiId: !Ref MyApi
      ResourceId: !Ref MyResource
      HttpMethod: GET
      Authorization: NONE
      Integration:
        HttpMethod: GET
        Type: MOCK
        RequestTemplates:
          application/json: |
            {"statusCode": "200"}
        Responses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Access-Control-Allow-Origin: "'*'"

  MyOptionsMethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      RestApiId: !Ref MyApi
      ResourceId: !Ref MyResource
      HttpMethod: OPTIONS
      Authorization: NONE
      Integration:
        HttpMethod: OPTIONS
        Type: MOCK
        RequestTemplates:
          application/json: |
            {"statusCode": "200"}
        Responses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Access-Control-Allow-Origin: "'*'"
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"

內容解密:

  1. MyApi 和 MyResource 資源定義:定義了一個名為 MyApi 的 API Gateway 和一個名為 MyResource 的資源。
  2. MyMethod 和 MyOptionsMethod 定義:定義了 GET 和 OPTIONS 方法,並設定了 MOCK 型別的整合請求和回應引數,以支援 CORS。
  3. RequestTemplatesResponses 設定:在整合請求中定義了回應範本,並在整合回應中設定了 CORS 相關的標頭引數。

使用 JavaScript SDK 實作和測試 Cognito 操作

在第四章「使用 Amazon Cognito 進行應用程式安全」中,我們已經瞭解瞭如何使用 AWS CLI 命令操作 Cognito。由於 Web 應用程式通常會在前端使用 JavaScript SDK 與 Cognito 互動,因此本章節將介紹如何使用 JavaScript SDK 進行 Cognito 操作,並使用 CodePen 進行測試。

準備工作

在開始之前,請確保您已經完成以下準備工作:

  1. 擁有一個可用的 AWS 帳戶。
  2. 在您的機器上安裝了 Node.js 和 npm。
  3. 下載 amazon-cognito-identity.min.js 檔案。
  4. 建立一個 S3 儲存桶並上傳 amazon-cognito-identity.min.js 檔案。
  5. 建立一個 Cognito 使用者池和客戶端。

下載 amazon-cognito-identity.min.js 檔案

首先,建立一個臨時資料夾並進入該資料夾,然後執行以下命令:

npm i amazon-cognito-identity-js

執行後,您應該會看到類別似以下的回應:

added 1 package in 2s

建立 S3 儲存桶並上傳 amazon-cognito-identity.min.js 檔案

建立一個 S3 儲存桶:

aws s3api create-bucket --bucket serverlesscookbook-cognito-files --profile admin

上傳 amazon-cognito-identity.min.js 檔案:

aws s3 cp amazon-cognito-identity.min.js s3://serverlesscookbook-cognito-files --profile admin

執行儲存桶政策以允許公開讀取存取:

aws s3api put-bucket-policy --bucket serverlesscookbook-cognito-files --policy file://s3-website-policy.json --profile admin

s3-website-policy.json 檔案的內容如下:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObjectAccess",
            "Effect": "Allow",
            "Principal": "*",
            "Action": ["s3:GetObject"],
            "Resource": ["arn:aws:s3:::serverlesscookbook-cognito-files/*"]
        }
    ]
}

建立 Cognito 使用者池和客戶端

建立一個 Cognito 使用者池:

aws cognito-idp create-user-pool --cli-input-json file://create-user-pool-cli-input.json --region us-east-1 --profile admin

create-user-pool-cli-input.json 檔案的內容如下:

{
    "PoolName": "javscript_pool",
    "Policies": {
        "PasswordPolicy": {
            "MinimumLength": 8,
            "RequireUppercase": true,
            "RequireLowercase": true,
            "RequireNumbers": true,
            "RequireSymbols": true
        }
    },
    "AutoVerifiedAttributes": ["email"],
    "AliasAttributes": ["email"],
    "EmailVerificationMessage": "Your verification code from MyApp is {####}",
    "EmailVerificationSubject": "Your verification code from MyApp",
    "UserPoolTags": {
        "Team": "Dev"
    }
}

建立一個使用者池客戶端:

aws cognito-idp create-user-pool-client --user-pool-id us-east-1_P8srRzYqn --client-name javscript-pool-client --explicit-auth-flows USER_PASSWORD_AUTH --region us-east-1 --profile admin

請將 user-pool-id 的值替換為您在前一步驟中建立的使用者池 ID。

使用 CodePen 執行 JavaScript SDK 程式碼

  1. 開啟 CodePen 並新增所需檔案的位置。
  2. 前往 https://codepen.io/
  3. 點選「Create」標籤,然後選擇「Pen」選項。
  4. 在新視窗中,點選「Settings」選單,選擇「Behaviour」標籤,然後取消勾選「Auto-Updating Preview」下的「Enabled」選項。
  5. 在「Settings」選單中,選擇「JavaScript」標籤,然後執行以下操作:
    • 搜尋「aws sdk」並選擇適當的 SDK。
    • 新增 amazon-cognito-identity.min.js 檔案的 URL(例如:https://s3.amazonaws.com/cognito-min-bucket/amazon-cognito-identity.min.js)。

使用 JavaScript SDK 進行 Cognito 操作

使用者註冊
var poolData = {
    UserPoolId: '<user pool id>',
    ClientId: '<client id>'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var attributeList = [];
var emailAttribute = {
    Name: 'email',
    Value: '<user email>'
};
attributeList.push(new AmazonCognitoIdentity.CognitoUserAttribute(emailAttribute));
userPool.signUp('heartin', 'Passw0rd$1', attributeList, null, function(err, result) {
    if (err) {
        console.log(JSON.stringify(err));
        alert(err.message || JSON.stringify(err));
        return;
    }
    var cognitoUser = result.user;
    console.log('user name is ' + cognitoUser.getUsername());
});

詳細解說:

  1. 建立使用者池物件:使用 AmazonCognitoIdentity.CognitoUserPool 建構函式建立一個使用者池物件,需要提供 UserPoolIdClientId
  2. 定義使用者屬性:建立一個屬性列表,並新增電子郵件屬性。
  3. 註冊使用者:呼叫 signUp 方法註冊使用者,需要提供使用者名稱、密碼、屬性列表和其他引數。
確認註冊的使用者
var poolData = {
    UserPoolId: '<user pool id>',
    ClientId: '<client id>'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
    Username: 'heartin',
    Pool: userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.confirmRegistration('698099', true, function(err, result) {
    if (err) {
        alert(err.message || JSON.stringify(err));
        return;
    }
    console.log('call result: ' + result);
});

詳細解說:

  1. 建立使用者物件:使用 AmazonCognitoIdentity.CognitoUser 建構函式建立一個使用者物件,需要提供使用者名稱和使用者池物件。
  2. 確認註冊:呼叫 confirmRegistration 方法確認註冊,需要提供驗證碼和其他引數。
登入應用程式
var authenticationData = {
    Username: 'heartin',
    Password: 'Passw0rd$1',
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var poolData = {
    UserPoolId: '<user pool id>',
    ClientId: '<client id>'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
    Username: 'heartin',
    Pool: userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
    onSuccess: function(result) {
        console.log('access token + ' + result.getAccessToken().getJwtToken());
    },
    onFailure: function(err) {
        alert(err.message || JSON.stringify(err));
    },
});

詳細解說:

  1. 建立驗證資料:建立一個驗證資料物件,需要提供使用者名稱和密碼。
  2. 建立驗證詳細資訊:使用 AmazonCognitoIdentity.AuthenticationDetails 建構函式建立一個驗證詳細資訊物件,需要提供驗證資料物件。
  3. 登入使用者:呼叫 authenticateUser 方法登入使用者,需要提供驗證詳細資訊物件和其他引數。