首页 > 解决方案 > 用于向 Google API (Swift) 验证服务帐户的 JWT 签名无效

问题描述

我是 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()

}

标签: swiftrestgoogle-cloud-platformjwt

解决方案


我需要使用从 .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
    }
}

推荐阅读