使用 Lambda 创建 Authorizer 对 API Gateway 访问进行鉴权

背景介绍

对于配置好的 API Gateway 资源来说, 默认会允许所有客户端进行访问. 我们可以配置 API key 进行简单的访问控制, 不过需要注意, API key 主要应用场景其实还是结合 Usage plan 对访问量进行控制, 并不提供鉴权的目的. 毕竟 API key 会作为一个静态的 Header x-api-key 存在请求的 Headers 里面. 还是需要通过 Lambda 创建函数作为 Authorizer 对发往 API Gateway 的请求进行具体的鉴权操作.

Lambda 函数作为 Authorizer 有两种方式:

注: 下面的 event 表示 Lambda 接受来自 API Gateway 作为 Trigger 的事件对象

基于 Request

Request 中可以包含的信息无非也就是 Headers (event.headers), QueryString (URL 地址中包含的问号后面的 K-V 变量, event.queryStringParameters), API 的 Stage 变量 (event.stageVariables). 在 Authorizer 函数中解析这些变量并加以逻辑判断, 确认是否允许此次请求.

基于 Token

通过客户端发送请求时包含的 Bearer token 进行逻辑判断, 常用 JSON Web Token (JWT), 或者 OAuth token. Token 信息存放在 event.authorizationToken

放上官方的工作流程示意图:

主要工作流

当 Client 端带着 Request 参数或者 Token 发起对 API Gateway 请求后, API Gateway 将这些信息包装在 Event 中传给 Lambda Authorizer 函数进行鉴权. 结果会是一个 JSON 格式的 IAM Policy 返回给 API Gateway, 其中包含 Principal identifier (用来识别客户端身份的唯一标识). 如果 Policy 允许, 则 API Gateway 放行请求到后面的 Integration (中文叫集成, 即原本 API 设计的 Resource 目标). 反之则会直接给客户端返回 403 或者 401 响应 (可自己定义). 这里还有一个 Cache 缓存的设计, 本文暂时先不展开讨论.

Lambda authorizer 输入数据

所谓的"输入"数据, 其实就是 API Gateway 作为 Trigger 调用 Lambda 函数时发送过去的 Event 对象.

Token 方式

json 复制代码
{
    "type":"TOKEN",
    "authorizationToken":"{caller-supplied-token}",
    "methodArn":"arn:aws:execute-api:{regionId}:{accountId}:{apiId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]"
}

Request 方式

json 复制代码
{
  "type": "REQUEST",
  "methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request",
  "resource": "/request",
  "path": "/request",
  "httpMethod": "GET",
  "headers": {
    "X-AMZ-Date": "20170718T062915Z",
    "Accept": "*/*",
    "HeaderAuth1": "headerValue1",
    "CloudFront-Viewer-Country": "US",
    "CloudFront-Forwarded-Proto": "https",
    "CloudFront-Is-Tablet-Viewer": "false",
    "CloudFront-Is-Mobile-Viewer": "false",
    "User-Agent": "..."
  },
  "queryStringParameters": {
    "QueryString1": "queryValue1"
  },
  "pathParameters": {},
  "stageVariables": {
    "StageVar1": "stageValue1"
  },
  "requestContext": {
    "path": "/request",
    "accountId": "123456789012",
    "resourceId": "05c7jb",
    "stage": "test",
    "requestId": "...",
    "identity": {
      "apiKey": "...",
      "sourceIp": "...",
      "clientCert": {
        "clientCertPem": "CERT_CONTENT",
        "subjectDN": "www.example.com",
        "issuerDN": "Example issuer",
        "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1",
        "validity": {
          "notBefore": "May 28 12:30:02 2019 GMT",
          "notAfter": "Aug  5 09:36:04 2021 GMT"
        }
      }
    },
    "resourcePath": "/request",
    "httpMethod": "GET",
    "apiId": "abcdef123"
  }
}

Lambda authorizer 输出数据

所谓"输出"数据, 即 Lambda 在进行逻辑处理之后, 最终要输出返回的数据, 其中 policyDocument 对应的是 IAM Policy 语法 JSON 内容, 除此之外还有额外的几个 Key.

json 复制代码
{
  "principalId": "yyyyyyyy", // The principal user identification associated with the token sent by the client.
  "policyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "execute-api:Invoke",
        "Effect": "Allow|Deny",
        "Resource": "arn:aws:execute-api:{regionId}:{accountId}:{apiId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]"
      }
    ]
  },
  "context": {
    "stringKey": "value",
    "numberKey": "1",
    "booleanKey": "true"
  },
  "usageIdentifierKey": "{api-key}"
}

实战演练

以下实现 Request 方式的 Authorizer, 使用 AWSCLI 和 Console 进行资源的创建和配置.

REST API 创建和配置过程

创建 REST API

bash 复制代码
aws apigateway create-rest-api --name "MockAPI" --description "An API with a mock resource" --endpoint-configuration types=REGIONAL
复制代码
{
    "id": "5g4hehluqi",
    "name": "MockAPI",
    "description": "An API with a mock resource",
    "createdDate": "2024-12-17T21:28:18+08:00",
    "apiKeySource": "HEADER",
    "endpointConfiguration": {
        "types": [
            "REGIONAL"
        ]
    },
    "disableExecuteApiEndpoint": false,
    "rootResourceId": "ck5z1k9xug"
}

获取根路径的资源 ID

bash 复制代码
aws apigateway get-resources --rest-api-id 5g4hehluqi
复制代码
{
    "items": [
        {
            "id": "ck5z1k9xug",
            "path": "/"
        }
    ]
}

在根路径创建一个资源, 对应路径 /cip

bash 复制代码
aws apigateway create-resource --rest-api-id 5g4hehluqi --parent-id ck5z1k9xug --path-part "cip"
复制代码
{
    "id": "qd1pk5",
    "parentId": "ck5z1k9xug",
    "pathPart": "cip",
    "path": "/cip"
}

/cip 资源添加 GET 方法

bash 复制代码
aws apigateway put-method --rest-api-id 5g4hehluqi --resource-id qd1pk5 --http-method GET --authorization-type NONE
复制代码
{
    "httpMethod": "GET",
    "authorizationType": "NONE",
    "apiKeyRequired": false
}

添加 Integration (集成), 访问目标 HTTP 端点

bash 复制代码
aws apigateway put-integration --rest-api-id 5g4hehluqi --resource-id qd1pk5 --http-method GET --type HTTP --integration-http-method GET --uri "http://cip.cc"
复制代码
{
    "type": "HTTP",
    "httpMethod": "GET",
    "uri": "http://cip.cc",
    "connectionType": "INTERNET",
    "passthroughBehavior": "WHEN_NO_MATCH",
    "timeoutInMillis": 29000,
    "cacheNamespace": "qd1pk5",
    "cacheKeyParameters": []
}

设置 Method Response

bash 复制代码
aws apigateway put-method-response --rest-api-id 5g4hehluqi --resource-id qd1pk5 --http-method GET --status-code 200 --response-models '{"application/json": "Empty"}'
复制代码
{
    "statusCode": "200",
    "responseModels": {
        "application/json": "Empty"
    }
}

设置 Integration Response

bash 复制代码
aws apigateway put-integration-response --rest-api-id 5g4hehluqi --resource-id qd1pk5 --http-method GET --status-code 200 --selection-pattern "" --response-templates '{"application/json": "$input.body"}'
复制代码
{
    "statusCode": "200",
    "selectionPattern": "",
    "responseTemplates": {
        "application/json": "$input.body"
    }
}

部署 API

bash 复制代码
aws apigateway create-deployment --rest-api-id 5g4hehluqi --stage-name dev
复制代码
{
    "id": "7zglrj",
    "createdDate": "2024-12-17T21:49:52+08:00"
}

测试访问

bash 复制代码
curl https://5g4hehluqi.execute-api.cn-northwest-1.amazonaws.com.cn/dev/cip/

Lambda 函数创建过程

Lambda console 创建 Python 3.13 Runtime 函数, 起名 MockAPI-Authorizer, 创建完成后记录 Function ARN arn:aws-cn:lambda:cn-northwest-1:000000000000:function:MockAPI-Authorizer

注: 以下 Python 代码由 ChatGPT 辅助生成, 基本无误一把通过

py 复制代码
import json

def lambda_handler(event, context):
    # Retrieve the 'password' from the request headers
    password = event.get('headers', {}).get('password', '')
    
    # Define the response policy based on the password value
    if password == '123456':
        # Allow the request
        effect = 'Allow'
    else:
        # Deny the request
        effect = 'Deny'
    
    # Prepare the IAM policy
    principal_id = 'user'  # You can use a unique identifier here
    method_arn = event['methodArn']  # The ARN of the method that triggered the authorizer
    
    # Create the policy document
    policy_document = {
        'Version': '2012-10-17',
        'Statement': [
            {
                'Action': 'execute-api:Invoke',
                'Effect': effect,
                'Resource': method_arn
            }
        ]
    }

    # Return the policy
    return {
        'principalId': principal_id,
        'policyDocument': policy_document
    }

关联 API 和 Authorizer




更新 Resource 中的方法开启 Authorization


重新部署 API

bash 复制代码
aws apigateway create-deployment --rest-api-id 5g4hehluqi --stage-name dev

测试非授权访问

bash 复制代码
curl -v https://5g4hehluqi.execute-api.cn-northwest-1.amazonaws.com.cn/dev/cip/

返回 401 Unauthorized

复制代码
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 401
< date: Tue, 17 Dec 2024 15:53:51 GMT
< content-type: application/json
< content-length: 26
< x-amzn-requestid: 0df4c146-062d-4172-b398-79abd6d93336
< x-amzn-errortype: UnauthorizedException
< x-amz-apigw-id: C8W2aFST5PgEnjQ=
<
* Connection #0 to host 5g4hehluqi.execute-api.cn-northwest-1.amazonaws.com.cn left intact
{"message":"Unauthorized"}

测试授权访问

bash 复制代码
curl -v -H "password: 123456" https://5g4hehluqi.execute-api.cn-northwest-1.amazonaws.com.cn/dev/cip/

返回 200 和目标 HTTP 页面代码

复制代码
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/2 200
< date: Tue, 17 Dec 2024 15:55:40 GMT
< content-type: application/json
< content-length: 3314
< x-amzn-requestid: fa0270f2-65ac-4768-83cb-81db5150cb19
< x-amz-apigw-id: C8XHYGL75PgEUUA=
< x-amzn-trace-id: Root=1-67619efb-3f6c452044132b282341ff5e;Parent=34799ffb91c22252;Sampled=0;Lineage=1d8b2fdd:0
<
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
        <head>
                <title>互连协议查询 - IP查询 - 查IP(www.cip.cc)</title>
                ...省略...
相关推荐
动感小麦兜29 分钟前
应用-常用工具部署命令
java·开发语言
日日行不惧千万里41 分钟前
IDEA 是用什么开发的?
java·ide·intellij-idea
weixin_446260851 小时前
Windows 安装原生安卓 App!无需模拟器,秒装谷歌商店!
android
fruge1 小时前
移动端 H5 兼容问题合集:iOS 与 Android 的差异化处理
android·ios
2501_915909061 小时前
iOS 上架需要什么东西?一次从准备清单到实操流程的完整技术拆解
android·macos·ios·小程序·uni-app·cocoa·iphone
百***06012 小时前
五大消息模型介绍(RabbitMQ 详细注释版)
java·rabbitmq·java-rabbitmq
转转技术团队2 小时前
MyBatis-Plus踩坑血泪史:那些年我们踩过的坑!
java·面试·mybatis
sg_knight3 小时前
IntelliJ IDEA 实用插件:GitToolBox 使用指南
java·ide·git·intellij-idea·插件·gittoolbox
青云交3 小时前
Java 大视界 -- Java 大数据机器学习模型在电商用户画像构建与精准营销中的应用
java·大数据·机器学习·电商·协同过滤·用户画像·精准营销
成都大菠萝3 小时前
Android层级分布
android