用于向 Google API (Swift) 验证服务帐户的 JWT 签名无效
Posted
技术标签:
【中文标题】用于向 Google API (Swift) 验证服务帐户的 JWT 签名无效【英文标题】:Invalid JWT signature for authenticating service account to Google API (Swift) 【发布时间】:2021-10-03 17:18:10 【问题描述】:我是 Google API 的新手;我正在尝试在没有用户交互的情况下检索服务帐户的访问令牌。我读过这样做的方法是生成一个 JWT,然后向 API 运行一个请求,但是当我将 JWT 发送给 Google 时,我不断收到此错误:
"error":"invalid_grant","error_description":"Invalid JWT Signature."
这是生成 JWT 的代码:
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.advanced(by: 1000)
let secret = "-----BEGIN PRIVATE KEY-----HERE IS A TON OF NUMBERS, SLASHES, PLUS SIGNS, GOING ON FOR HUNDREDS OF CHARACTERS. THIS WAS TAKEN FROM THE KEY DOWNLOADED AS A JSON FROM THE CREDENTIALS PAGE FOR THE SERVICE ACCOUNT IN GOOGLE CLOUD.\n-----END PRIVATE KEY-----\n"
let privateKey = SymmetricKey(data: secret.data(using: .utf8)!)
let headerJSONData = try! JSONEncoder().encode(Header())
let headerBase64String = headerJSONData.urlSafeBase64EncodedString()
let payloadJSONData = try! JSONEncoder().encode(Payload())
let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString()
let toSign = (headerBase64String + "." + payloadBase64String).data(using: .utf8)!
let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
let signatureBase64String = Data(signature).urlSafeBase64EncodedString()
let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".")
下面是正在运行的请求:
if let url = URL(string: "https://oauth2.googleapis.com/token?grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=\(token)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request) data, response, error in
if data != nil
print("Data: \(String(data: data!, encoding: .utf8)!)")
.resume()
【问题讨论】:
1) 您不能直接使用服务帐户 JSON 密钥文件中的私钥 string。格式为 PEM。您必须加载 PEM 密钥并将其转换为库所需的格式(通常是二进制字符串)。检查您的库中是否有类似 getKeyStringFromPEM() 2) 私钥是非对称的(RSA 私钥),而不是对称的。 3) 我不会用 Swift 编写代码,所以希望我的提示会有所帮助。 在标题中设置“alg”="RS256 并稍后加载私钥,这都可以,但是然后您使用 HMAC我需要使用从 .p12 文件转换的 PEM 密钥切换到 RSA 和 RS256。使用 JWT.io 库有所帮助。 这是我的工作代码:
import JWTKit // from jwt.io
func generateJWT() -> String?
struct Header: JWTPayload
enum CodingKeys: String, CodingKey
case alg = "alg"
case type = "typ"
var alg = "RS256"
var type: String = "JWT"
func verify(using signer: JWTSigner) throws
// print(self.expireTime > Date().timeIntervalSince1970)
fatalError()
struct Payload: JWTPayload
enum CodingKeys: String, CodingKey
case email = "iss"
case scope = "scope"
case aud = "aud"
case createdAt = "iat"
case expireTime = "exp"
var email: String = "content-uploader@*************.iam.gserviceaccount.com"
var scope: String = "https://www.googleapis.com/auth/devstorage.full_control https://www.googleapis.com/auth/cloud-platform"
var aud: String = "https://oauth2.googleapis.com/token"
var createdAt: Double = Date().timeIntervalSince1970
var expireTime: Double = Date().advanced(by: 1000).timeIntervalSince1970
func verify(using signer: JWTSigner) throws
print(self.expireTime > Date().timeIntervalSince1970)
fatalError()
do
if let certificatePath = Bundle.main.path(forResource: "pem-file-name", ofType: "pem")
let certificateUrl = URL(fileURLWithPath: certificatePath)
let certififcateData = try Data(contentsOf: certificateUrl)
let signers = JWTSigners()
let key = try RSAKey.private(pem: certififcateData)
signers.use(.rs256(key: key))
// MARK: HEADER NOT USED
let header = Header()
let payload = Payload()
let jwt = try signers.sign(payload)
// let jwt = try signers.sign(payload)
print("JWT: \(jwt)")
return jwt
else
return nil
catch
print(error)
return nil
【讨论】:
以上是关于用于向 Google API (Swift) 验证服务帐户的 JWT 签名无效的主要内容,如果未能解决你的问题,请参考以下文章
在Swift中使用Google Calendar API时收到错误“超出未经身份验证的每日限制”
除了 AppDelegate 和 UIViewController 之外的一个类中的 GIDSignInDelegate 使用示例,用于 swift 中的 Google 身份验证
我如何注册Google Classroom API的推送通知?
Xamarin.Auth OAuth Google API,用于Android的用户身份验证