news 2026/10/2 17:29:14

App Engine 应用间请求身份校验实战:基于 X-Appengine-Inbound-Appid 与 ID Token 的双通道认证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
App Engine 应用间请求身份校验实战:基于 X-Appengine-Inbound-Appid 与 ID Token 的双通道认证
  • 示例工程

【免费下载链接】python-docs-samples

Code samples used on cloud.google.com

项目地址:https://gitcode.com/GitHub_Trending/py/python-docs-samples
点击查看免费下载

导读

本文基于 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.app
  • runtime: 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"}头成功访问的正面用例,可与本示例的负面用例互补理解。

部署与端到端使用流程

  1. 部署接收端:将 incoming 目录部署为 App Engine Python 2.7 应用,并把真实调用方 GCP 项目 ID 填入MainPage.allowed_app_ids。
  2. 部署发送端:将 urlfetch 示例 部署为 Python 3.7 应用(注意仓库中该路径位于appengine/standard_python3/migration/urlfetch/)。
  3. 发起调用:在发送端表单中输入接收端应用的 URL,提交后发送端获取默认服务账号的 ID Token,以Authorization: Bearer头调用接收端。
  4. 观察结果: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

项目地址:https://gitcode.com/GitHub_Trending/py/python-docs-samples
点击查看免费下载
上一篇:AI Toolkit性能优化:GPU内存管理与训练加速技巧
下一篇:3步让《模拟人生1》在现代显示器上完美重生:宽屏补丁完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 17:28:26

# 少写脚本才是好产品 —— 绑定与脚本的分工> 鲲鹏恒控是一套工业 HMI / SCADA 上位机组态软件。它的脚本引擎很强(真 C# + 易语言式简写),但这一篇想说的恰好相反:>> **

# 少写脚本才是好产品 —— 绑定与脚本的分工> 鲲鹏恒控是一套工业 HMI / SCADA 上位机组态软件。它的脚本引擎很强(真 C# 易语言式简写),但这一篇想说的恰好相反:>> **衡量一套组态软件好不好用,有个反直觉的指标 —— 你写脚本的次数在变少。**【配图&#xff1a;左…

作者头像 李华
网站建设 2026/10/2 17:24:49

FreeRTOS实战教程-第二章

第二章 任务创建与调度 —— 改造 01_LED 2.1 实验回顾:裸机版 LED 闪烁 01_LED 工程的实现非常直接——主循环里两个灯交替亮灭,靠 delay_ms 控制节奏: while (1) {LED0(0); /* 点亮 LED0(低电平点亮) */LED1(1); /* 熄灭 LED1 */delay_ms(500); …

作者头像 李华
网站建设 2026/10/2 17:24:30

AI自动提取会议纪要中的行动项:如何校验负责人与截止时间的准确性

把会议记录变成可执行的行动清单&#xff0c;应该让 AI 同时提取要做什么、谁接受了任务、原话中的期限和出处&#xff0c;再把决定、未决问题分开。发布到任务系统之前&#xff0c;逐项回到记录核对&#xff0c;不能只看表格是否整齐。 “Leon 会发检查清单”“应该有人查一下…

作者头像 李华
网站建设 2026/10/2 17:24:29

智能的传感

在数字经济与实体经济深度融合的当下&#xff0c;智能化转型已经成为各行各业高质量发展的必然趋势。人工智能、大数据、物联网等前沿技术的快速迭代&#xff0c;让“万物互联、万物智能”从概念逐步落地为现实。而支撑这一切智能变革的底层核心技术&#xff0c;正是智能传感技…

作者头像 李华