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

Swift调用Google API服务账号认证报Invalid JWT signature错误如何解决

错误原因
  • 签名算法不匹配:JWT Header中声明的签名算法为RS256(即SHA256WithRSA,非对称签名算法,需要使用RSA私钥进行签名),但代码中使用了HMAC<SHA256>(对称消息认证码算法),二者完全不兼容,导致Google端校验签名失败。
  • 私钥处理逻辑错误:直接将带PEM格式头尾的私钥字符串转为UTF8数据作为对称密钥使用,没有对PEM格式的私钥做解码处理,没有提取出真正的RSA私钥二进制数据。
  • 请求参数格式不符合规范:将grant_type和assertion参数拼接在URL查询串中,但请求头声明了application/x-www-form-urlencoded格式,这类参数应该放在POST请求的body体中,不符合规范的传参也可能导致校验失败。
修复方案
  1. 首先处理PEM格式的RSA私钥,去除头尾标识、换行符后做Base64解码,得到原始RSA私钥数据,再通过Security框架生成RSA私钥对象。
  2. 替换HMAC签名逻辑为RSA-SHA256签名逻辑,使用生成的RSA私钥对JWT header.payload串做签名。
  3. 调整请求参数的传递方式,将参数放入POST请求的httpBody中。

修复后参考代码如下:

import Security
import Foundation

extension Data {
    func urlSafeBase64EncodedString() -> String {
        return base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
    }
}

struct Header: Encodable {
    let alg = "RS256"
    let typ = "JWT"
}

struct Payload: Encodable {
    let iss = "content-uploader@project-id.iam.gserviceaccount.com"
    let scope = "https://www.googleapis.com/auth/devstorage.full_control"
    let aud = "https://oauth2.googleapis.com/token"
    let iat = Date().timeIntervalSince1970
    let exp = Date().timeIntervalSince1970 + 3600 // JWT最大有效期为1小时
}

// 处理PEM私钥
let pemKey = "-----BEGIN PRIVATE KEY-----你的私钥内容\n-----END PRIVATE KEY-----\n"
let cleanedKey = pemKey
    .replacingOccurrences(of: "-----BEGIN PRIVATE KEY-----", with: "")
    .replacingOccurrences(of: "-----END PRIVATE KEY-----", with: "")
    .replacingOccurrences(of: "\n", with: "")
    .trimmingCharacters(in: .whitespacesAndNewlines)

guard let keyData = Data(base64Encoded: cleanedKey) else {
    fatalError("私钥解码失败")
}

// 生成RSA私钥对象
let attributes: [CFString: Any] = [
    kSecAttrKeyType: kSecAttrKeyTypeRSA,
    kSecAttrKeyClass: kSecAttrKeyClassPrivate,
    kSecAttrKeySizeInBits: 2048
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateWithData(keyData as CFData, attributes as CFDictionary, &error) else {
    fatalError("RSA私钥创建失败: \(error!.takeRetainedValue() as Error)")
}

// 生成JWT header和payload
let headerJSONData = try! JSONEncoder().encode(Header())
let headerBase64 = headerJSONData.urlSafeBase64EncodedString()

let payloadJSONData = try! JSONEncoder().encode(Payload())
let payloadBase64 = payloadJSONData.urlSafeBase64EncodedString()

let toSign = "\(headerBase64).\(payloadBase64)".data(using: .utf8)!

// RSA-SHA256签名
guard let signature = SecKeyCreateSignature(privateKey, .rsaSignatureMessagePKCS1v15SHA256, toSign as CFData, &error) as Data?, error == nil else {
    fatalError("签名失败: \(error!.takeRetainedValue() as Error)")
}
let signatureBase64 = signature.urlSafeBase64EncodedString()

let jwt = "\(headerBase64).\(payloadBase64).\(signatureBase64)"

修复后请求代码如下:

guard let url = URL(string: "https://oauth2.googleapis.com/token") else {
    fatalError("URL无效")
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
// 参数放入body中
let params = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=\(jwt)"
request.httpBody = params.data(using: .utf8)

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    if let data = data, let result = String(data: data, encoding: .utf8) {
        print("返回结果: \(result)")
    }
}
task.resume()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.10 19:51:02