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

Swift中如何创建OAuth2.0调用FCM v1 API?Vapor服务如何调用FCM发通知?

嘿,我刚好在Vapor项目里实现了FCM v1 API的OAuth2授权,给你一步步拆解怎么做,完全适配Swift环境:

在Vapor(Swift)中实现FCM v1 API的OAuth2授权

一、先搞懂核心前提

  • FCM v1 API 不支持用Server key/Legacy key调用,必须使用基于服务账号的OAuth2访问令牌
  • 我们需要通过JWT签名向Google的OAuth2服务器换取临时访问令牌,再用这个令牌调用FCM接口

二、准备Firebase服务账号密钥

  1. 登录Firebase控制台,进入你的项目
  2. 点击「设置」→「服务账号」→「生成新的私钥」
  3. 下载JSON格式的密钥文件,放到Vapor项目的Resources目录下(记得把这个文件加入.gitignore!)

三、配置Vapor项目依赖

在Package.swift里添加必要的依赖:

dependencies: [
    .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
    .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0"),
    .package(url: "https://github.com/vapor/async-http-client.git", from: "1.0.0")
],
targets: [
    .target(
        name: "App",
        dependencies: [
            .product(name: "Vapor", package: "vapor"),
            .product(name: "JWTKit", package: "jwt-kit"),
            .product(name: "AsyncHTTPClient", package: "async-http-client")
        ]
    ),
    // ...其他targets
]

四、实现令牌获取逻辑

1. 定义服务账号和令牌相关的结构体

import Vapor
import JWTKit

// 解析服务账号JSON的结构体
struct FirebaseServiceAccount: Codable {
    let type: String
    let projectId: String
    let privateKeyId: String
    let privateKey: String
    let clientEmail: String
    let clientId: String
    let authUri: String
    let tokenUri: String
    let authProviderX509CertUrl: String
    let clientX509CertUrl: String
}

// JWT断言结构体,用于生成签名
struct FCMAuthClaims: JWTPayload {
    let iss: String       // 服务账号的client_email
    let scope: String     // FCM权限范围
    let aud: String       // OAuth2令牌端点地址
    let exp: Date         // 令牌过期时间(最多1小时)
    let iat: Date         // 令牌签发时间
    
    // 验证过期时间
    func verify(using signer: JWTSigner) throws {
        try self.exp.verifyNotExpired()
    }
}

// 令牌响应结构体
struct FCMAccessTokenResponse: Codable {
    let accessToken: String
    let tokenType: String
    let expiresIn: Int
    
    enum CodingKeys: String, CodingKey {
        case accessToken = "access_token"
        case tokenType = "token_type"
        case expiresIn = "expires_in"
    }
}

2. 加载服务账号到Vapor应用

在configure.swift里添加:

func configure(_ app: Application) throws {
    // 加载服务账号JSON
    let serviceAccountPath = app.directory.resourcesDirectory.appending("service-account.json")
    guard let serviceAccountData = try? Data(contentsOf: serviceAccountPath) else {
        fatalError("找不到Firebase服务账号JSON文件,请检查Resources目录")
    }
    let serviceAccount = try JSONDecoder().decode(FirebaseServiceAccount.self, from: serviceAccountData)
    // 把服务账号存储到应用中,方便后续调用
    app.storage.set(FirebaseServiceAccount.self, to: serviceAccount)
    
    // ...其他配置
}

3. 添加获取令牌的扩展方法

给Application加个异步方法,用来获取FCM访问令牌:

extension Application {
    func fetchFCMAccessToken() async throws -> String {
        guard let serviceAccount = self.storage.get(FirebaseServiceAccount.self) else {
            fatalError("服务账号未初始化")
        }
        
        // 构建JWT断言
        let claims = FCMAuthClaims(
            iss: serviceAccount.clientEmail,
            scope: "https://www.googleapis.com/auth/firebase.messaging",
            aud: "https://oauth2.googleapis.com/token",
            exp: Date().addingTimeInterval(3600), // 1小时有效期
            iat: Date()
        )
        
        // 用服务账号的私钥初始化RS256签名器
        let privateKey = try RSAKey.private(pem: serviceAccount.privateKey)
        let signer = JWTSigner.rs256(key: privateKey)
        
        // 生成签名后的JWT字符串
        let jwt = try signer.sign(claims)
        
        // 向Google OAuth2服务器发送请求换取令牌
        let client = self.http.client
        var request = ClientRequest(method: .POST, url: "https://oauth2.googleapis.com/token")
        // 提交表单数据
        request.content = try .query([
            "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
            "assertion": jwt
        ])
        
        let response = try await client.send(request)
        // 解析响应获取access_token
        let tokenResponse = try await response.content.decode(FCMAccessTokenResponse.self)
        return tokenResponse.accessToken
    }
}

五、调用FCM v1 API发送通知

实现一个发送通知的函数,用上上面获取的令牌:

func sendFCMNotification(app: Application, deviceToken: String, title: String, body: String) async throws {
    let accessToken = try await app.fetchFCMAccessToken()
    let projectId = app.storage.get(FirebaseServiceAccount.self)!.projectId
    
    // 构建FCM请求
    var request = ClientRequest(
        method: .POST,
        url: "https://fcm.googleapis.com/v1/projects/\(projectId)/messages:send"
    )
    // 设置Authorization头
    request.headers.add(name: .authorization, value: "Bearer \(accessToken)")
    request.headers.add(name: .contentType, value: "application/json")
    
    // 构建通知内容
    let notificationPayload: [String: Any] = [
        "message": [
            "token": deviceToken,
            "notification": [
                "title": title,
                "body": body
            ],
            // 如果需要自定义数据,可以加"data"字段
            "data": [
                "custom_key": "custom_value"
            ]
        ]
    ]
    
    request.content = try .json(notificationPayload)
    
    // 发送请求
    let response = try await app.http.client.send(request)
    // 检查响应状态
    guard response.status.isSuccess else {
        let errorDetails = try await response.content.decode([String: Any].self)
        throw Abort(.internalServerError, reason: "FCM发送失败:\(errorDetails)")
    }
}

六、优化建议

  • 缓存令牌:因为令牌有效期是1小时,没必要每次请求都重新生成,可以用Vapor的Cache系统缓存令牌,每次先检查缓存是否有效,无效再重新获取
  • 权限配置:确保你的服务账号在Google Cloud IAM中拥有「Cloud Messaging Admin」角色,否则会出现权限错误
  • 生产环境安全:不要把服务账号JSON硬编码或提交到版本控制,生产环境可以用环境变量读取密钥内容,或者使用Google Cloud Secret Manager

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:14:09