You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Google服务账户调用Google Document API认证失败:权限范围为空/缺失

Swift服务账户认证调用Google Docs API时的invalid_scope错误解决思路

问题背景

Swift应用需通过Google服务账户认证,调用Google Documents API读取文档。因无Swift官方库,采用SwiftJWT生成签名JWT请求https://oauth2.googleapis.com/token获取访问令牌,但返回invalid_scope错误。

服务账户信息:<my_project>@<...>.iam.gserviceaccount.com,已分配'owner'和'viewer'的IAM角色,使用该账户绑定的私钥签名JWT。

错误信息

error = "invalid_scope";"error_description" = "Empty or missing scope not allowed.";

相关代码

import SwiftJWT
import Alamofire

func getAuthToken() {
    let header = Header(kid: "<myiD>")
    
    let claims = ClaimsStandardJWT(
        iss: "<my_server_account>@<my_project>.iam.gserviceaccount.com",
        aud: ["https://oauth2.googleapis.com/token"],
        exp: Date().addingTimeInterval(3600),
        iat: Date()
     )
    
    var jwt = JWT(header: header, claims: claims)

    let privateKey =  """
    -----BEGIN PRIVATE KEY-----
    <my_private_key>
    -----END PRIVATE KEY-----
    """

    guard let privateKeyData = privateKey.data(using: .utf8) else {
        print("Failed to convert string to data")
        return
    }
    var signedJWT = ""
    do {
        signedJWT = try jwt.sign(using: .rs256(privateKey: privateKeyData))
    } catch  {
        print("Failed to sign JWT: \(error)")
    }
        
    // Exchange the JWT token for a Google OAuth2 access token
    let headers: HTTPHeaders = ["Content-Type": "application/x-www-form-urlencoded"]
    let params: Parameters = [
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": signedJWT,
        "scope": "https://www.googleapis.com/auth/documents.readonly"
    ]
    
    AF.request("https://oauth2.googleapis.com/token",
               method: .post,
               parameters: params,
               encoding: URLEncoding.httpBody,
               headers: headers).responseJSON { response in
        print(response.result)
        switch response.result {
        case .success(let value):
            let json = value as? [String: Any]
            if let json = json {
                let accessToken = json["access_token"] as? String
                if let accessToken = accessToken {
                    fetchGoogleDocContent(with: accessToken)
                }
            }
        case .failure(let error):
            print("Error getting access token: \(error)")
        }
}

解决思路

  • 修正JWT Claims的scope和aud字段:
    Google服务账户的JWT认证要求scope必须包含在JWT的Payload中,而非仅在POST请求参数里。同时aud字段需为单个字符串,不能是数组。需自定义Claims结构体:

    struct GoogleServiceAccountClaims: Claims {
        let iss: String
        let aud: String
        let exp: Date
        let iat: Date
        let scope: String
    }
    

    初始化Claims时传入正确参数:

    let claims = GoogleServiceAccountClaims(
        iss: "<my_server_account>@<my_project>.iam.gserviceaccount.com",
        aud: "https://oauth2.googleapis.com/token",
        exp: Date().addingTimeInterval(3600),
        iat: Date(),
        scope: "https://www.googleapis.com/auth/documents.readonly"
    )
    
  • 确认API启用状态:登录Google Cloud控制台,检查Google Docs API是否已启用,未启用的话需手动开启。

  • 验证JWT内容:对签名后的JWT进行本地解码,检查Payload中是否包含正确的scope和aud字段,确保没有拼写错误。

  • 确认scope有效性:https://www.googleapis.com/auth/documents.readonly是Google Docs只读访问的正确scope,确认无拼写错误。

内容的提问来源于stack exchange,提问作者David M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 22:37:48