在上一篇文章中,我們介紹了API Gateway 以及 Lambda 這組常用的 Serverless 後端服務。並透過最小專案走過了 Cognito 發 Token → API Gateway 驗證 → Lambda 取得使用者資訊 的流程。
今天,我們來進入Console 建立 API Gateway 與 Lambda,進入 Cognito 串接的細節,走過整個登入流程建置可能會遇到的問題。
DemoFunction
值得注意的是在 Permission 區域提及會同時創建一個 Role 使得 Lambda 有權限將 Log 傳送至 Cloudwatch,這同時也代表在創建 Lambda 的同時將會創建一個 Cloudwatch 來監控這個執行實例。
在 Lambda 程式碼區域撰寫程式時 可以透過"#"方式註解與 Amazon Q (AWS的AI助手)對話,他會根據你的需求以及上下文將程式碼補全 (例如用#要求補全 CORS 標頭)
你可能會發現 Lambda 本身還提供 Function URL 的配置,可為你的 Function 提供 Endpoint,不過與API Gateway 相比,Function URL 還是有它的限制,既然都在 Free Tier 為何不選好的呢?
功能 | Lambda Function URL | API Gateway |
---|---|---|
端點數量 | 每個 URL 綁定一個 Lambda(沒有內建多路由) | 一個 Gateway 可管理多個路由與方法(RESTful / WebSocket / HTTP API) |
授權方式 | IAM / 自行在程式碼中實作 JWT 驗證 | IAM / Cognito / Lambda Authorizer / API Key / WAF |
CORS | 支援,但配置較簡單 | 完整設定,能細緻控制來源、方法、Header |
進階功能 | 無 (要在 Lambda 程式碼中處理驗證/轉換) | 支援 Throttling、Rate limiting、流量管理、整合 WAF、安全性更完整 |
使用情境 | Demo、小工具、內部服務、單一端點 | 正式專案、需要多路由、需要雲原生 API 管理與安全控制 |
而程式碼的維護通常會在自己的本地 IDE 可以透過容器 or CDK 將程式碼給封裝起來串接 CI/CD 流程 (這裡用CDK)
4.建立一個 Route 建立了一個 GET 方法在 /Hello
到這裡就成功建立了一個與 Lambda 連接的 API Gateway,只要透過 /Hello 端口就會觸發部署在 Lambda 中的 Function,而接下來我們要設定 Cognito 的 Token 驗證
在 AWS Console 中 Integration with lambda 會自動為 API Gateway 建立所需的權限,但在 CDK 需要額外配置
6.綁定已經建立的 User Pool
我們需要回到 Cognito 的 Console 找到Token signing key URL貼給 API Gateway 並指定
Authorization
Header 驗證 Token
Issuer URL的部分記得將複製下來的後綴
/.well-known/jwks.json
給去掉, Audience 的部分則是填入 App client 的 Client ID
完成後,就可以在前端帶著 JWT Token 呼叫這個 API
除了 Console 點點點之外,我們也能用 CDK 自動化部署。
在這裡我們以昨天的Demo專案為範例。
在以下的程式碼中建立了一個 Lambda,並透過 API Gateway 連接,最後綁上 Cognito Authorizer。
from aws_cdk import (
Stack,
aws_apigateway as apigw,
aws_lambda as _lambda,
aws_cognito as cognito,
aws_iam as iam,
CfnOutput,
)
from constructs import Construct
from typing import Optional, Dict, Any
class JwtTestStack(Stack):
def __init__(self, scope: Construct, construct_id: str, *, env_vars: Optional[Dict[str, Any]] = None, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
env_vars = env_vars or {}
# 從環境變數讀取 User Pool Id(.env -> app.py 載入)
user_pool_id = env_vars.get("REACT_APP_COGNITO_USER_POOL_ID", "ap-east-1_xxxxxxxxx")
user_pool = cognito.UserPool.from_user_pool_id(self, "ImportedUserPool", user_pool_id)
# 透過 CDK 定義的 Cognito User pool 資源直接定義授權方
authorizer = apigw.CognitoUserPoolsAuthorizer(self, "JwtAuthorizer", cognito_user_pools=[user_pool])
# 部署 Lambda 資源,程式碼則是從檔案路徑中引入
fn = _lambda.Function(self, "JwtEchoFunction",
runtime=_lambda.Runtime.PYTHON_3_12,
handler="handler.lambda_handler",
code=_lambda.Code.from_asset("../lambda/jwt_test")
)
# 部署 API Gateway 資源
api = apigw.RestApi(
self,
"JwtTestApi",
# 這裡是定義為REST API
rest_api_name="JWT Test API",
description="Echo JWT claims",
# 定義CORS規則
default_cors_preflight_options=apigw.CorsOptions(
allow_origins=apigw.Cors.ALL_ORIGINS,
allow_methods=["GET", "OPTIONS"],
allow_headers=["Authorization", "Content-Type"]
)
)
# 創建 ROUTE /echo 並綁定 Lamnbda Function
echo = api.root.add_resource("echo")
echo_lambda = apigw.LambdaIntegration(fn)
echo.add_method("GET", echo_lambda,
authorizer=authorizer,
authorization_type=apigw.AuthorizationType.COGNITO
)
# 明確授權 API Gateway 調用 Lambda(避免權限錯誤)
fn.add_permission(
"ApiGatewayInvoke",
principal=iam.ServicePrincipal("apigateway.amazonaws.com"),
action="lambda:InvokeFunction",
source_arn=f"arn:aws:execute-api:{Stack.of(self).region}:{Stack.of(self).account}:{api.rest_api_id}/*/GET/echo"
)
CfnOutput(self, "ApiUrl", value=api.url)
fetch
呼叫 API 時,要記得加上 Authorization: Bearer <token>
CORS policy: No 'Access-Control-Allow-Origin'
Authorization
headerlambda:InvokeFunction
今天我們完成了:
到這裡,我們的「安全守門員」就正式上線了 🎉