如何在 Google Cloud VM 上使用服务账号凭证安全调用自托管服务

落磊小哥_5979

落磊小哥_5979

2026-01-30

390人浏览

原创

如何在 Google Cloud VM 上使用服务账号凭证安全调用自托管服务

本文详解为何基于服务账号的 id token 认证无法与 compute engine vm 的自托管服务(如 redis api)正常通信,并指出根本原因在于 google 的服务账号身份验证机制不适用于直接面向公网 ip 的非托管服务,同时提供正确实现方案与关键避坑指南。

Google Cloud 的 IDTokenCredentials(即 service_account.IDTokenCredentials.from_service_account_file)并非用于向任意公网 IP + 端口发起“带身份认证的 HTTP 请求”,而是专为 Google 托管服务(如 Cloud Run、Cloud Functions、IAP-protected backends)设计的身份验证机制。其核心逻辑是:生成一个短期 JWT ID Token,以 target_audience 作为该 Token 的 aud(受众)声明,由 Google 的身份验证中间件(如 IAP 或 Cloud Run 的内置 Auth)负责校验该 Token 的签名、时效性及 aud 是否匹配其自身服务标识。

然而,你的 Compute Engine VM 上运行的是完全自托管的容器服务(如 Flask/FastAPI + Redis),它不具备任何内置的 Google ID Token 验证能力。你当前的代码:

target_audience = f"https://{ip}:{port}"
creds = service_account.IDTokenCredentials.from_service_account_file(
    KEY_FILE, target_audience=target_audience)

会生成一个 aud 为 https://:8000 的 Token,但你的 VM 应用既不会解析 Authorization Header 中的 Bearer Token,也不会校验其签名或 aud 字段——它只是静默忽略该 Token,继续等待有效业务请求体。此时,请求看似“发出去了”,但服务端因未收到预期格式的 payload(或因 Token 校验逻辑缺失导致路由/鉴权层阻塞),迟迟不返回响应,最终触发客户端 ReadTimeout。

✅ 正确方案:使用服务账号密钥进行服务端身份校验(而非客户端 Token 注入)

  1. 服务端(VM 容器内)需主动验证请求来源:
    在你的应用中(例如 Python FastAPI),添加中间件,从 Authorization: Bearer 提取 JWT,并使用 Google 的公钥集(https://www.googleapis.com/oauth2/v3/certs)验证其签名、aud、iss(应为 https://accounts.google.com)和有效期。

  2. 客户端仍可使用 IDTokenCredentials,但 target_audience 必须是服务端可识别的逻辑标识(非 IP):
    实际上,target_audience 应设为一个服务标识符(如 my-vm-service),并在服务端硬编码校验该值,而非动态拼接 IP。因为 IP 可能变化,且 Google 不允许 aud 为裸 IP(违反 OIDC 规范)。

示例服务端验证逻辑(Python + PyJWT):

python-flask-vue-creator
python-flask-vue-creator

创建 Flask + Vue.js 全栈项目,采用单端口架构、uv 依赖管理、结构化日志和生产级前端设计。当用户要求以下操作时触发:创建新的 Flask+Vue 项目、搭建 Web 应用、构建全栈 Python+Vue 应用、初始化 Flask Web 项目、设置单端口 Flask 前后端项目。触发词:新建项目、创建项目、搭建项目、Flask项目、Vue项目。

下载
from google.auth import crypt
from google.auth.transport import requests as google_requests
import jwt
import requests

# 获取 Google 公钥(缓存复用)
def get_google_public_keys():
    resp = requests.get("https://www.googleapis.com/oauth2/v3/certs")
    return resp.json()

def verify_id_token(token: str, expected_audience: str = "my-vm-service") -> dict:
    keys = get_google_public_keys()
    header = jwt.get_unverified_header(token)
    key_id = header.get("kid")
    signing_key = None
    for key in keys["keys"]:
        if key["kid"] == key_id:
            signing_key = crypt.RSASigner.from_string(key["x5c"][0])
            break
    if not signing_key:
        raise ValueError("Invalid key ID")

    payload = jwt.decode(
        token,
        signing_key,
        algorithms=["RS256"],
        audience=expected_audience,
        issuer="https://accounts.google.com",
    )
    return payload

# FastAPI 中间件示例
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
    auth_header = request.headers.get("Authorization")
    if not auth_header or not auth_header.startswith("Bearer "):
        return JSONResponse({"error": "Unauthorized"}, status_code=401)

    token = auth_header.split(" ")[1]
    try:
        claims = verify_id_token(token, expected_audience="my-vm-service")
        request.state.user_email = claims.get("email")
    except Exception as e:
        return JSONResponse({"error": "Invalid token"}, status_code=401)

    return await call_next(request)

客户端保持类似结构,但修正 target_audience:

# ✅ 正确:audience 是逻辑服务名,非 IP
target_audience = "my-vm-service"  # 与服务端校验一致
creds = service_account.IDTokenCredentials.from_service_account_file(
    "vm-key.json", target_audience=target_audience)

authed_session = AuthorizedSession(creds)
response = authed_session.post(
    url=f"http://{ip}:8000/get_attributes",  # 注意:服务端若无 HTTPS,此处用 http
    json=uuids,
    timeout=10  # 增大超时,避免误报 ReadTimeout
)

⚠️ 关键注意事项:

  • 不要禁用 SSL 验证(verify=False):这会带来严重安全风险;若服务端使用自签名证书,请配置正确的 CA Bundle。
  • VPC 防火墙规则与服务账号无关:0.0.0.0/0 规则仅控制网络可达性,不提供身份认证能力;服务账号认证必须由应用层显式实现。
  • 避免将服务账号密钥(.json)部署到生产 VM:应改用 VM 关联的服务账号(Compute Engine metadata server),通过 google.auth.default() 自动获取凭据,更安全且免密钥管理。

总结:ReadTimeout 的本质不是网络问题,而是服务端未实现 ID Token 校验逻辑导致请求挂起。真正的解决方案是——将服务账号认证从“客户端单向注入”转变为“服务端主动验证”,并确保 audience 语义清晰、传输协议匹配、超时设置合理。

相关专题

更多
flask框架如何搭建
flask框架如何搭建

搭建步骤:1、安装Python和Pip;2、创建虚拟环境;3、安装Flask;4、创建Flask应用;5、运行应用;6、访问应用。想了解更多flask框架的相关内容,可以阅读本专题下面的文章。

2024.06.27

4075

8

Python Flask框架
Python Flask框架

本专题专注于 Python 轻量级 Web 框架 Flask 的学习与实战,内容涵盖路由与视图、模板渲染、表单处理、数据库集成、用户认证以及RESTful API 开发。通过博客系统、任务管理工具与微服务接口等项目实战,帮助学员掌握 Flask 在快速构建小型到中型 Web 应用中的核心技能。

2025.08.25

6284

10

Python Flask Web框架与API开发
Python Flask Web框架与API开发

本专题系统介绍 Python Flask Web框架的基础与进阶应用,包括Flask路由、请求与响应、模板渲染、表单处理、安全性加固、数据库集成(SQLAlchemy)、以及使用Flask构建 RESTful API 服务。通过多个实战项目,帮助学习者掌握使用 Flask 开发高效、可扩展的 Web 应用与 API。

2025.12.15

316

16

什么是中间件
什么是中间件

中间件是一种软件组件,充当不兼容组件之间的桥梁,提供额外服务,例如集成异构系统、提供常用服务、提高应用程序性能,以及简化应用程序开发。想了解更多中间件的相关内容,可以阅读本专题下面的文章。

2024.05.11

689

5

Golang 中间件开发与微服务架构
Golang 中间件开发与微服务架构

本专题系统讲解 Golang 在微服务架构中的中间件开发,包括日志处理、限流与熔断、认证与授权、服务监控、API 网关设计等常见中间件功能的实现。通过实战项目,帮助开发者理解如何使用 Go 编写高效、可扩展的中间件组件,并在微服务环境中进行灵活部署与管理。

2025.12.18

644

18

ThinkPHP中间件机制与请求拦截处理实践
ThinkPHP中间件机制与请求拦截处理实践

本专题围绕 ThinkPHP 中间件体系展开,深入讲解中间件的定义、注册与执行流程。内容包括全局中间件与路由中间件的区别、请求前后处理逻辑、自定义中间件开发以及权限验证与日志处理应用。通过实际案例,帮助开发者掌握中间件在项目中的核心作用与最佳实践。

2026.03.31

458

17

json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

2023.08.07

2235

5

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.23

3602

1

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

2023.10.13

1196

3

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程