PAYATHON 2026

如何授权 SDK 访问 AWS API Gateway 后端 API?

支付阿杰

明确结论

不能把 Android 游戏中的 SDK 视为可信的 machine-to-machine 客户端,也不能在 SDK 中保存长期密钥、OAuth client_secret 或 AWS Access Key。APK、运行内存和网络流量都可能被分析,随 SDK 分发的共享凭据最终可能被提取并滥用。

推荐使用以下架构:

Android SDK
    │ 购买凭证 + 游戏方短期身份凭证
    ▼
游戏开发者的后端(可信服务)
    │ OAuth 2.0 Client Credentials / AWS SigV4
    ▼
AWS API Gateway
    ▼
支付后端

如果无法要求接入方部署后端,只能采用设备级风险控制方案,例如短期临时凭证、Play Integrity API、请求签名、防重放和限流。这些措施可以提高攻击成本,但无法让 SDK 成为真正可信的客户端。

为什么 SDK 不能安全保存机器凭据

machine-to-machine 认证通常假设调用方是能够保守秘密的机密客户端,例如服务器、容器或受控工作负载。Android SDK 不具备这一条件:

  • APK 可以被反编译;
  • client_secret、API Key 和 AWS Access Key 可以被静态提取;
  • Native Library、代码混淆和字符串加密只能延缓提取;
  • 攻击者可以 Hook SDK、读取运行时内存或直接重放请求;
  • 如果所有游戏共用同一凭据,一次泄露就会影响全部接入方;
  • 为每个游戏分配独立凭据只能缩小影响范围,无法解决客户端不能保密的问题。

API Gateway 的 API Key 主要用于识别调用方、配额管理和用量控制,不应作为认证凭据。CORS 也不是移动端 API 的安全边界。

推荐方案:由游戏后端代表 SDK 调用 API

为每个游戏开发者注册一个服务端客户端,并分配独立的 client_id 和 client_secret。这些凭据只部署在游戏开发者的后端,不进入 Android 应用。

典型流程如下:

  1. Android SDK 从 Google Play Billing 获取 purchaseToken、productId 等购买信息。
  2. SDK 将购买信息和当前游戏会话凭证发送到游戏开发者自己的后端。
  3. 游戏后端验证玩家会话、请求完整性和业务参数。
  4. 游戏后端通过 OAuth 2.0 Client Credentials 获取短期 Access Token,或者使用 AWS IAM 凭据对请求进行 SigV4 签名。
  5. 游戏后端调用 API Gateway。
  6. 支付后端向 Google Play 服务端验证购买凭证,并进行幂等处理。
  7. 结果沿原路径返回 SDK。

不能只相信 SDK 上报的“支付成功”状态。最终购买状态应由可信后端向 Google Play 验证。

方案一:OAuth 2.0 Client Credentials

可以使用支持 Client Credentials Grant 的授权服务器签发短期 JWT,再由 API Gateway 的 JWT Authorizer 或 Lambda Authorizer 验证 Token。

能否直接使用 API Gateway 的 JWT Authorizer,取决于 API Gateway 类型、Token 格式和所用的身份提供商。如果条件不满足,可以改用 Lambda Authorizer。

游戏后端获取 Token:

curl -X POST 'https://auth.example.com/oauth2/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u "${CLIENT_ID}:${CLIENT_SECRET}" \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'scope=payments:start'

随后调用 API Gateway:

curl -X POST 'https://api.example.com/payments' \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 1eab44b1-97a2-43ee-8507-709d60ed6028' \
  -d '{
    "productId": "coins_100",
    "purchaseToken": "token-returned-by-google-play"
  }'

Token 的有效期应较短,并通过 scope、aud 和客户端身份限制权限。例如,只允许调用 payments:start,而不是整个 API。

方案二:AWS IAM 与 SigV4

如果调用方后端运行在 AWS 上,可以为其配置 IAM Role,并把 API Gateway 路由设为 AWS_IAM 授权。调用方使用临时 AWS 凭据,通过 SigV4 对请求签名。

Node.js 示例:

import { defaultProvider } from "@aws-sdk/credential-provider-node";
import { SignatureV4 } from "@smithy/signature-v4";
import { HttpRequest } from "@smithy/protocol-http";
import { Sha256 } from "@aws-crypto/sha256-js";

const region = "ap-southeast-1";
const url = new URL("https://api.example.com/payments");

const body = JSON.stringify({
  productId: "coins_100",
  purchaseToken: "token-returned-by-google-play"
});

const signer = new SignatureV4({
  credentials: defaultProvider(),
  region,
  service: "execute-api",
  sha256: Sha256
});

const request = new HttpRequest({
  method: "POST",
  protocol: url.protocol,
  hostname: url.hostname,
  path: url.pathname,
  headers: {
    host: url.hostname,
    "content-type": "application/json",
    "idempotency-key": crypto.randomUUID()
  },
  body
});

const signedRequest = await signer.sign(request);

const response = await fetch(url, {
  method: signedRequest.method,
  headers: signedRequest.headers,
  body
});

if (!response.ok) {
  throw new Error(`API request failed: ${response.status}`);
}

console.log(await response.json());

IAM Policy 应遵循最小权限原则,只允许访问所需的 API、Stage、HTTP Method 和 Resource Path。

不要把 IAM User 的长期 Access Key 编译进 SDK。即使通过 STS 下发临时凭据,也必须先解决“谁有资格获得这些凭据”的引导认证问题。

Android SDK 应承担什么职责

SDK 负责采集购买信息并调用可信后端,不应持有服务端机密。例如:

data class PurchaseRequest(
    val productId: String,
    val purchaseToken: String,
    val requestId: String
)

suspend fun submitPurchase(
    api: GameBackendApi,
    sessionToken: String,
    productId: String,
    purchaseToken: String
): PurchaseResult {
    return api.submitPurchase(
        authorization = "Bearer $sessionToken",
        request = PurchaseRequest(
            productId = productId,
            purchaseToken = purchaseToken,
            requestId = UUID.randomUUID().toString()
        )
    )
}

这里的 sessionToken 用于识别当前游戏会话或玩家,并不是保存在 SDK 中的全局秘密。它可以由游戏后端签发,同时设置较短的有效期和有限的权限。

没有游戏后端时的折中方案

如果业务要求 SDK 直接调用 API Gateway,可以采用公开客户端流程:

  1. SDK 请求 Google Play Integrity Token。
  2. SDK 将 Integrity Token、应用包名、证书摘要、时间戳和随机数发送到后端。
  3. 后端验证 Integrity Token,同时检查应用身份和设备风险信号。
  4. 验证通过后,后端签发一个有效期只有数分钟的受限 Token。
  5. SDK 使用该 Token 调用 API Gateway。
  6. 后端根据请求频率、额度、设备、应用和行为实施风控。

请求中可以加入防重放字段:

{
  "timestamp": 1787971200,
  "nonce": "80b06031-2782-41cf-9556-95431a1b2642",
  "productId": "coins_100",
  "purchaseToken": "token-returned-by-google-play"
}

服务端需要验证:

  • timestamp 是否在允许的时间窗口内;
  • nonce 是否已被使用;
  • Token 的 iss、aud、exp 和权限范围;
  • 包名和签名证书是否属于已注册应用;
  • 购买凭证是否已经处理;
  • 请求频率和异常行为是否超过阈值。

Play Integrity 提供的是设备和应用完整性的风险信号,并不等同于不可伪造的客户端身份。Root、Hook、代理转发和真实设备农场仍可能绕过部分限制,因此,高价值支付操作不能只依赖设备证明。

支付接口还需要的保护

无论采用哪种认证方式,还应实施以下控制:

  • 由服务端验证 Google Play purchaseToken;
  • 使用 purchaseToken、订单号或业务请求 ID 实现幂等;
  • 每个开发者使用独立的客户端身份,避免共享凭据;
  • 缩短 Access Token 的有效期,并限制 audience 和 scope;
  • 按客户端、应用、设备和 IP 分层限流;
  • 记录客户端 ID、请求 ID 和授权结果,但不要记录完整 Token;
  • 支持密钥轮换、吊销,以及对单个开发者进行隔离;
  • 使用 HTTPS,不要自行设计加密协议;
  • 不要把 API Key、OAuth client_secret、私钥或 AWS Access Key 放进 APK;
  • 在服务端校验金额、商品、币种和订单状态,不能信任客户端参数。

SDK 可以证明“它带来了某些信号”,但无法安全保守一个所有安装实例都能使用的秘密。真正的 machine-to-machine 授权应发生在两个受控后端之间。

备注:内容仅供参考。