- 示例工程
【免费下载链接】python-docs-samples
Code samples used on cloud.google.com
导读
本文基于 python-docs-samples 仓库中的 incoming 示例应用,讲解如何在 App Engine(Python 2.7)端接收并校验来自其他 App Engine 应用的请求身份。你将掌握两套互补的调用方身份验证方案:Python 2.7 调用方自带的可信X-Appengine-Inbound-Appid请求头,以及 Python 3.7 调用方携带的默认服务账号 ID Token(OAuth2 Bearer)。读完本文,你可以直接复刻出"接收端校验 + 发送端携带凭证"的完整 App Engine 应用间安全通信闭环。
背景:为什么 App Engine 应用间调用需要显式身份校验
在 App Engine 服务端到服务端的调用场景中,一个应用(调用方)常常需要访问另一个 App Engine 应用(被调用方)暴露的接口。如果被调用方只依赖普通鉴权,就无法确认请求到底来自可信的兄弟应用,还是来自外部攻击者。
本示例正是解决这一问题的"接收端样板":被调用方维护一份allowed_app_ids白名单,只有当请求携带了可信身份凭证、且凭证解析出的应用 ID 在白名单内时,才返回受保护的内容;否则一律返回 HTTP 403。
双通道校验机制:两种调用方、两种凭证
仓库中的 README.md 明确给出了两条校验路径:
- Python 2.7 调用方(使用
urlfetch):其发起的请求会携带一个由 App Engine 运行时注入、值得信任的X-Appengine-Inbound-Appid请求头。运行时会在其他请求到达应用前剥离该头,因此凡是带这个头的请求,其应用 ID 声明是可信的。 - Python 3.7 调用方:不再使用
urlfetch,而是为调用方应用的默认服务账号获取一个 ID Token,并将其放入Authorization: Bearer <token>请求头中。接收方通过验证该 Token 来确认调用方身份。
配合使用的发送端示例是仓库中的 standard_python3/migration/urlfetch 示例应用,它负责以携带合法Authorization头的方式调用本接收端应用。
接收端实现:逐行拆解 incoming 示例
1. 目录与文件结构
该示例位于 appengine/standard/migration/incoming/,核心文件如下:
- main.py:Web 应用主逻辑,包含身份解析与白名单校验
- app.yaml:Python 2.7 运行时的 App Engine 配置
- appengine_config.py:vendor 库加载配置
- main_test.py:基于 WebTest 的单元测试
- requirements.txt:运行依赖(
google-auth==2.17.3等) - requirements-test.txt:测试依赖(
WebTest==3.0.4等)
2. 身份解析核心函数get_app_id
main.py 中的get_app_id(request)是整套校验的关键,它按优先级处理两种凭证:
def get_app_id(request): # 路径一:Python 2.7 urlfetch 请求携带的可信头 incoming_app_id = request.headers.get("X-Appengine-Inbound-Appid", None) if incoming_app_id is not None: return incoming_app_id # 路径二:解析 Authorization: Bearer <id_token> auth_header = request.headers.get("Authorization", None) if auth_header is None: return None bearer, token = auth_header.split() if bearer.lower() != "bearer": return None try: info = id_token.verify_oauth2_token(token, requests.Request()) service_account_email = info["email"] incoming_app_id, domain = service_account_email.split("@") if domain != "appspot.gserviceaccount.com": # 非 App Engine 服务账号则拒绝 return None else: return incoming_app_id except Exception as e: logging.warning("Request has bad OAuth2 id token: {}".format(e)) return None实现要点:
- 头优先策略:
X-Appengine-Inbound-Appid由运行时注入且无法被伪造,因此存在即直接采用,无需额外验证。 - Bearer 解析严格性:
Authorization头必须严格符合Bearer <token>格式,split()结果与bearer小写比对,格式不符直接返回None。 - Token 验证:调用
id_token.verify_oauth2_token(token, requests.Request())完成签发方校验(依赖google-auth库),再从中取出服务账号邮箱。 - 域名白名单校验:邮箱必须形如
<app-id>@appspot.gserviceaccount.com,即必须是 App Engine 默认服务账号,否则拒绝。此处的domain比对是防止伪造邮箱的关键一环。 - 异常兜底:Token 过期、签名错误等情况统一捕获并记录
logging.warning,返回None,保证接口不会因解析异常而崩溃。
3. 请求处理器与白名单逻辑
main.py 中的MainPage展示了完整的"解析 → 校验 → 放行/拒绝"链路:
class MainPage(webapp2.RequestHandler): allowed_app_ids = ["other-app-id", "other-app-id-2"] def get(self): incoming_app_id = get_app_id(self.request) if incoming_app_id is None: self.abort(403) if incoming_app_id not in self.allowed_app_ids: self.abort(403) self.response.write("This is a protected page.") app = webapp2.WSGIApplication([("/", MainPage)], debug=True)allowed_app_ids是示例白名单,实际部署时应替换为真实的调用方应用 ID(即 GCP 项目 ID)。- 两类失败(凭证缺失/非法、应用 ID 不在白名单)均通过
self.abort(403)返回 HTTP 403。 - 只有校验通过才输出
This is a protected page.,这行文本正是发送端示例回显给用户的内容。 - 应用入口通过
webapp2.WSGIApplication暴露为main.app,与 app.yaml 中的script: main.app对应。
4. 运行配置:app.yaml 与 appengine_config.py
app.yaml 将应用声明为 Python 2.7 标准环境:
runtime: python27 threadsafe: yes api_version: 1 libraries: - name: ssl version: latest handlers: - url: .* script: main.appruntime: python27表明这是迁移场景中的旧版接收端。libraries: ssl显式引入 SSL 库,满足google-auth在 Python 2.7 下安全发起 HTTPS Token 校验请求的底层需求。handlers将全部路径路由到main.app。
appengine_config.py 使用google.appengine.ext.vendor加载lib目录中通过pip install -t lib安装的第三方依赖(如google-auth),这是 Python 2.7 标准环境引入外部包的标准做法。
5. 依赖清单
requirements.txt 的关键依赖:
google-auth==2.17.3 requests==2.34.2; python_version >= '3.10'google-auth提供id_token.verify_oauth2_token与google.auth.transport.requests.Request,是 ID Token 校验的实现基础。
发送端实现:Python 3.7 调用方如何携带凭证
与接收端配对的 standard_python3/migration/urlfetch 示例应用 展示了从 Python 3.7 侧获取并携带 ID Token 的完整流程:
from google.auth.transport import requests as reqs from google.oauth2 import id_token import requests @app.route("/", methods=["POST"]) def make_request(): url = request.form["url"] token = id_token.fetch_id_token(reqs.Request(), url) resp = requests.get(url, headers={"Authorization": f"Bearer {token}"}) message = f"Response when calling {url}:\n\n" message += resp.text return message, 200, {"Content-type": "text/plain"}id_token.fetch_id_token(reqs.Request(), url)为调用方默认服务账号按目标 URL 的 audience 获取 ID Token——无需手工管理密钥或服务账号文件,直接运行在 App Engine 上即可获得默认凭据。- 将 Token 组装为
Authorization: Bearer <token>头后通过requests.get发起调用,正好对接收端get_app_id中bearer, token = auth_header.split()的解析逻辑。 - 该应用同时提供
GET /表单页(templates/index.html),让用户输入目标 App Engine 应用 URL 后即可测试调用,响应以纯文本回显。
测试验证:WebTest 如何守护身份校验逻辑
main_test.py 给出了针对接收端的最小可验证测试:
import webtest import main def test_get(): app = webtest.TestApp(main.app) try: response = app.get("/") assert response.status_int == 403 except webtest.app.AppError as e: assert "403 Forbidden" in str(e)- 不带任何身份头直接请求
/,断言返回 403——验证了"无凭证一律拒绝"的安全基线。 - 测试通过
webtest.TestApp(main.app)在进程内模拟 HTTP 请求,无需真实部署。 - 仓库中另有一处同源参考:standard/app_identity/incoming/main_test.py 展示了携带
{"X-Appengine-Inbound-Appid": "other-app-id"}头成功访问的正面用例,可与本示例的负面用例互补理解。
部署与端到端使用流程
- 部署接收端:将 incoming 目录部署为 App Engine Python 2.7 应用,并把真实调用方 GCP 项目 ID 填入
MainPage.allowed_app_ids。 - 部署发送端:将 urlfetch 示例 部署为 Python 3.7 应用(注意仓库中该路径位于
appengine/standard_python3/migration/urlfetch/)。 - 发起调用:在发送端表单中输入接收端应用的 URL,提交后发送端获取默认服务账号的 ID Token,以
Authorization: Bearer头调用接收端。 - 观察结果:Token 有效且应用 ID 在白名单内时,页面回显
This is a protected page.;凭证缺失、Token 非法或应用 ID 不在白名单时,接收端返回 403。
安全边界与迁移启示
X-Appengine-Inbound-Appid头只能由 Python 2.7 环境的urlfetch产生,App Engine 运行时会从外部请求中剥离该头,因此它天然防伪造;但 Python 3.7 应用不再具备该能力,必须改用 ID Token 方案,这正是 README 中所述"替换内建可信头" 的迁移要点。- ID Token 校验依赖时钟同步与
google-auth的公开证书校验,任何校验异常都会被捕获并降级为 403,属于"默认拒绝"的安全设计。 - 生产环境应避免使用示例中的
debug=True,并将allowed_app_ids白名单迁移到配置或环境变量管理,避免硬编码。
相关资源
- incoming README(本文主文档)
- 接收端主实现
- 接收端运行配置
- 接收端测试
- Python 3.7 发送端示例
- app_identity 下的同源参考示例
- 示例工程
【免费下载链接】python-docs-samples
Code samples used on cloud.google.com
相关推荐
google-api-php-client 的 ID Token 认证实战:无需网络调用的用户身份校验原理与 verifyIdToken 用法
google api php client 的 ID Token 认证实战:无需网络调用的用户身份校验原理与 verifyIdToken 用法 导读 本文围绕
后端Feathers 集成 Firebase 认证:基于自定义 OAuthStrategy 校验 ID Token 的完整实战
Feathers 集成 Firebase 认证:基于自定义 OAuthStrategy 校验 ID Token 的完整实战 Firebase 是 Feather
后端Web框架Payload JWT 认证:如何签发带 role 的 JWT 并校验请求身份
Payload JWT 认证:如何签发带 role 的 JWT 并校验请求身份 Payload 的认证体系内置了 JWT 策略:在启用认证的 Collectio
后端CMS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考