第3章 OAuth 2.0 认证流程理解

Google Play API 采用 OAuth 2.0 作为身份授权协议。对于服务器端自动化场景(如批量管理应用、读取销售报告),推荐使用 服务账号 + JWT 授权模式。本章将解析 JWT 的结构、scope 的作用、令牌获取流程以及 access_ 的有效期管理。

3.1 核心概念

概念说明
JWT 授权模式服务账号使用 RSA 私钥签名一个 JWT 断言,向 Google 的 OAuth 令牌端点换取 access_,无需用户交互。
OAuth scope以 URI 形式标识 API 权限范围,例如 https://www.googleapis.com/auth/androidpublisher。单一 可包含多个 scope。
access_用于调用 Google Play API 的临时凭证,默认有效期 3600 秒(1小时),过期后必须重新获取。
refresh_服务账号模式不提供 refresh_; 过期后需重新生成 JWT 并再次调用令牌端点。

3.2 流程概览

从创建 JWT 到成功调用 API 共 5 个关键步骤:

  1. 使用服务账号的私钥构建 JWT 头部和载荷(预设 iss、scope、aud、exp、iat)。
  2. 通过 RSA SHA256 对 JWT 签名,得到 assertion。
  3. 向 https://oauth2.googleapis.com/ 发送 POST 请求,携带 grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer 和 assertion。
  4. 从 JSON 响应中提取 access_ 和 expires_in(秒)。
  5. 在 HTTP 头中携带 Authorization: Bearer <access_> 调用 Google Play API。
关键点:JWT 的 aud 必须为 https://oauth2.googleapis.com/,scope 必须与所需 API 权限一致。签名用私钥,公钥由 Google 验证。

3.3 JWT 结构与签名参数

字段示例值说明
alg"RS256"签名算法,服务账号固定使用 RS256
iss"service-account@project.iam.gserviceaccount.com"服务账号的邮箱
scope"https://www.googleapis.com/auth/androidpublisher"所需权限,多个 scope 用空格连接
aud"https://oauth2.googleapis.com/"令牌端点地址
iat当前 Unix 时间戳签发时间
expiat + 3600过期时间,不可超过 1 小时

3.4 请求令牌示例(Python)

使用 google-auth 库自动处理 JWT 签名和令牌交换。

from google.oauth2 import service_account
import googleapiclient.discovery

SCOPES = ["https://www.googleapis.com/auth/androidpublisher"]
SERVICE_ACCOUNT_FILE = "path/to/service-account-key.json"

credentials = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=SCOPES
)

# credentials 对象自动携带 access_,有效期内复用
# 手动获取 access_(调试用)
if credentials.expired:
    credentials.refresh(None)   # 服务账号模式 refresh 内部会重新走 JWT 流程
access_ = credentials.
print("Access :", access_)

3.5 手动构造 JWT 并获取 (curl)

适用于语言无现成库的调试场景:

# 1. 生成 JWT assertion(需借助 jwt.io 或 openssl)
HEADER='{"alg":"RS256","typ":"JWT"}'
PAYLOAD='{"iss":"your-sa@project.iam.gserviceaccount.com","scope":"https://www.googleapis.com/auth/androidpublisher","aud":"https://oauth2.googleapis.com/","iat":'$(date +%s)',"exp":'$(($(date +%s) + 3600))'}'
# 2. 拼接并签名(私钥文件 private.key)
JWT=$(echo -n "$HEADER.$PAYLOAD" | openssl dgst -sha256 -sign private.key -binary | base64 | tr -d '=' | tr '/+' '_-')
# 3. 请求令牌
curl -s -X POST https://oauth2.googleapis.com/ \
  -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$JWT"

成功响应示例:

{
  "access_": "ya29.c.b0AXx...",
  "expires_in": 3599,
  "_type": "Bearer"
}

3.6 流程示意图

下方 SVG 展示服务账号模式的核心数据流:

服务账号 JWT 授权流程 服务账号 JWT → Access 流程 服务账号私钥 (JSON 文件中的 private_key) 构造 + 签名 JWT header.payload.signature 签名 POST / grant_type + assertion 返回 access_ expires_in = 3599 调用 Play API Authorization: Bearer 缓存 (内存) 检查 expires_in 避免重复请求 access_ 过期后 重新生成 JWT 再次请求

3.7 调试与常见错误

错误码原因解决方式
invalid_grantJWT 签名错误、已过期、iss 未授权等检查私钥匹配、系统时间同步、服务账号授权范围
invalid_scopescope 格式错误或未启用对应 API确保 scope URI 正确,在 Google Cloud Console 中启用 Google Play Android Developer API
400 Bad Request: unauthorized_client服务账号未在 Play Console 中添加在 Play Console > 用户和权限 > 邀请服务账号
返回正常但 API 返回 403scope 不足或资源未授权确认服务账号具有相关应用的访问权限

3.8 性能与稳定性优化

  • 缓存 access_:在内存或本地存储中保存 及获取时间戳,剩余有效期 > 60 秒时复用,避免每请求都去换取。
  • JWT 签名缓存:JWT 本身有 1 小时有效期,可在签发后缓存到过期前,不必每次重新签名(但注意不要泄露私钥)。
  • 异步获取:在高并发场景使用异步 HTTP 库提前刷新 ,避免同步阻塞。
  • 重试机制:遇到 5xx 或网络错误时,以指数退避重试令牌请求,间隔 1s、2s、4s。
注意:服务账号模式下 没有 refresh_。access_ 过期后必须重新构造 JWT 并调用令牌端点。务必确保系统时间与 NTP 同步,否则 iat 和 exp 偏差会导致 invalid_grant。

3.9 部署检查清单

  1. 确认服务账号邮箱已添加到 Google Play Console(用户和权限)。
  2. 确认私钥文件(JSON)安全存储在服务器,权限设置为 600。
  3. 验证 audience 和 scope 参数不允许外部配置篡改。
  4. 实现 缓存层并记录日志,便于定位过期问题。
  5. 设置报警监控,当连续 3 次获取 失败时触发告警。