如何为 Google App Engine 应用配置或访问 SSL?
结论
Google App Engine 应用可以通过 HTTPS 对外提供服务,也可以通过 HTTPS 调用支付服务商的 API。一般无须在 App Engine 实例中直接安装或读取 SSL 证书的私钥,也不应该这样做。
处理信用卡支付时,建议采用以下方式:
- 为 App Engine 默认域名或自定义域名启用 HTTPS。
- 使用 Stripe、Adyen、PayPal 等符合 PCI DSS 要求的支付服务商。
- 让用户直接在浏览器中将信用卡信息提交给支付服务商,或者跳转到服务商托管的结账页面。
- App Engine 后端只接收支付令牌、订单状态和通过签名验证的 webhook,不接触完整卡号和 CVV。
这种设计符合 App Engine 的托管运行模式,也能大幅缩小 PCI DSS 的合规范围。
为什么不能像普通服务器那样安装证书
App Engine 是托管平台。应用通常无法操作底层 Web 服务器,也不能自行修改 TLS 监听端口、证书文件路径或系统级网络配置。因此,传统服务器上的配置方式通常不适用,例如:
ssl_certificate /etc/ssl/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
TLS 连接应在 Google 管理的入口层终止,再由平台将请求转发给应用。应用代码只需处理 HTTP 请求,无须直接完成 TLS 握手。
如果使用自定义域名,可以通过平台提供的托管证书功能启用 HTTPS。具体配置入口和支持范围可能会随着 Google Cloud 控制台及 App Engine 产品策略调整,应以当前项目控制台和官方文档为准。
配置 HTTPS 的基本步骤
1. 使用 App Engine 默认域名
App Engine 应用通常会获得一个类似下面的默认地址:
https://PROJECT_ID.REGION_ID.r.appspot.com
实际域名格式可能与应用的创建时间和区域有关,请以 Google Cloud 控制台中显示的应用 URL 为准。
默认域名的 TLS 证书由 Google 管理,应用无须自行上传证书。
2. 为自定义域名启用 HTTPS
一般需要完成以下步骤:
- 在 Google Cloud 项目中验证域名所有权。
- 将自定义域名映射到 App Engine 应用。
- 按照控制台要求配置 DNS 记录。
- 等待托管 SSL 证书签发并生效。
- 使用浏览器或 TLS 检查工具确认域名匹配,并验证证书链。
DNS 配置和证书签发不一定会立即完成。在证书状态变为有效之前,不要将生产支付流量切换到该域名。
3. 强制使用 HTTPS
应用可以检查转发协议,并将 HTTP 请求重定向到 HTTPS。下面是一个 Flask 示例:
from flask import Flask, redirect, request
app = Flask(__name__)
@app.before_request
def require_https():
forwarded_proto = request.headers.get("X-Forwarded-Proto", "")
if forwarded_proto != "https":
return redirect(
request.url.replace("http://", "https://", 1),
code=301,
)
是否需要这段逻辑取决于具体的部署配置。使用 X-Forwarded-Proto 时,只能信任平台入口写入的请求头。在其他代理环境中,不能无条件信任客户端提供的同名请求头。
还可以发送 HSTS 请求头:
@app.after_request
def add_security_headers(response):
response.headers["Strict-Transport-Security"] = (
"max-age=31536000; includeSubDomains"
)
return response
启用 includeSubDomains 前,要确认所有子域名都支持 HTTPS。HSTS 配置错误可能导致用户在有效期内无法通过 HTTP 访问相关域名。
推荐的支付处理方式
更稳妥的做法是使用支付服务商托管的结账页面。支付流程可以设计为:
用户浏览器
├── HTTPS ──> App Engine:创建订单
├── HTTPS ──> 支付服务商:输入并提交信用卡信息
└── HTTPS <── 支付结果页面
支付服务商
└── 签名 webhook ──> App Engine:确认支付状态
完整卡号、有效期和 CVV 不会经过 App Engine 后端。后端只保存以下信息:
- 内部订单号
- 支付服务商生成的 payment ID 或 token
- 支付金额和币种
- 支付状态
- 必要的审计记录
不要保存 CVV。其他银行卡数据能否保存以及如何保存,应遵守支付服务商的规则、PCI DSS 要求和业务所在地适用的法规。
创建托管支付会话的示例
以下示例使用 Stripe Checkout 展示常见的实现结构。API 版本、SDK 初始化方式和参数可能发生变化,生产环境应按照支付服务商的当前文档进行调整:
import os
import stripe
from flask import Flask, jsonify
app = Flask(__name__)
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
@app.post("/api/checkout")
def create_checkout():
session = stripe.checkout.Session.create(
mode="payment",
line_items=[
{
"price": os.environ["STRIPE_PRICE_ID"],
"quantity": 1,
}
],
success_url="https://example.com/payment/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url="https://example.com/payment/cancel",
)
return jsonify({"checkout_url": session.url})
浏览器收到 checkout_url 后,跳转到支付服务商的页面:
async function startCheckout() {
const response = await fetch("/api/checkout", {
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
if (!response.ok) {
throw new Error("Unable to create checkout session");
}
const data = await response.json();
window.location.assign(data.checkout_url);
}
密钥应通过 Secret Manager、平台密钥配置或受保护的环境变量注入,不能写入源码,也不能提交到版本库。
必须验证支付 webhook
不能只根据用户被重定向到”支付成功”页面,就认定订单已经付款。用户可能伪造 URL、关闭页面,或者在支付完成前中断跳转。
订单状态应由通过签名验证的 webhook 更新:
import os
import stripe
from flask import Flask, request
app = Flask(__name__)
webhook_secret = os.environ["STRIPE_WEBHOOK_SECRET"]
@app.post("/webhooks/stripe")
def stripe_webhook():
payload = request.get_data()
signature = request.headers.get("Stripe-Signature", "")
try:
event = stripe.Webhook.construct_event(
payload,
signature,
webhook_secret,
)
except (ValueError, stripe.error.SignatureVerificationError):
return "invalid webhook", 400
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
payment_id = session["payment_intent"]
# 使用事务或唯一约束,保证重复 webhook 不会重复发货。
mark_order_as_paid(payment_id)
return "", 200
mark_order_as_paid() 必须进行幂等处理。支付服务商可能重复发送同一个事件,因此需要保存 event ID,或者为 payment ID 建立唯一约束,避免重复记账和重复发货。
App Engine 调用支付 API
App Engine 后端可以通过 HTTPS 请求支付服务商的 API。例如:
import os
import requests
response = requests.post(
"https://api.payment-provider.example/v1/payments",
headers={
"Authorization": f"Bearer {os.environ['PAYMENT_API_KEY']}",
"Idempotency-Key": "order-20260901-0001",
},
json={
"amount": 1999,
"currency": "CNY",
"payment_token": "token_from_provider",
},
timeout=15,
)
response.raise_for_status()
payment = response.json()
生产代码还要处理连接超时、重试、限流和结果不确定等情况。创建扣款等非幂等操作应使用支付服务商提供的 idempotency key,网络超时后不能直接无条件重试。
安全注意事项
- 除非团队已经具备相应的 PCI DSS 合规能力,否则不要在自己的支付表单中接收完整卡号和 CVV。
- 不要记录信用卡信息、支付 token、签名请求头、API secret 或完整的 webhook body。
- 金额必须由服务端根据商品和订单重新计算,不能直接采用浏览器提交的金额。
- 金额应使用整数形式的最小货币单位。例如,
1999表示19.99。不要使用二进制浮点数计算货币。 - 验证 webhook 签名,并根据需要限制事件的时间窗口,防止伪造和重放。
- 支付成功后的发货、充值或权益开通必须支持幂等处理。
- 测试环境和生产环境的密钥、webhook endpoint 及商品 ID 必须完全隔离。
- 定期轮换密钥,并限制可以读取生产密钥的账号和服务身份。
- HTTPS 只能保护传输过程,不能代替身份认证、权限控制、输入校验、审计和 PCI DSS 合规。
App Engine 的限制不会妨碍应用安全地处理支付。无须将 SSL 证书安装到应用实例中。应使用平台管理的 HTTPS,并由合规的支付服务商采集和处理敏感的银行卡数据。
备注:内容仅供参考。
