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

iOS端获取OAuth2 Access Token遇阻:FCM从Legacy API迁Http V1求助

解决iOS端FCM Http V1迁移的Token获取问题

核心误区澄清

你之前的Android实现存在安全风险:ServiceAccount JSON包含敏感的服务端密钥,绝对不能放在客户端(Android/iOS)代码或设备中,一旦泄露会导致Firebase项目被滥用。正确的流程应该是:

  • 客户端(Android/iOS)仅获取FCM的设备注册令牌(Registration Token)
  • 服务端(PHP)使用ServiceAccount JSON,通过Google Auth API获取Access Token,再用该Token调用FCM Http V1接口统一处理推送

iOS端正确操作步骤

  1. 获取FCM设备注册令牌
    在iOS项目中集成Firebase Messaging后,通过以下代码获取设备令牌并发送至PHP服务器:

    import FirebaseMessaging
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        // 初始获取令牌
        Messaging.messaging().token { token, error in
            if let error = error {
                print("获取FCM令牌失败: \(error)")
            } else if let token = token {
                self.sendTokenToServer(token: token)
            }
        }
        return true
    }
    
    // 监听令牌刷新
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        guard let token = fcmToken else { return }
        sendTokenToServer(token: token)
    }
    
    // 发送令牌到PHP服务器
    private func sendTokenToServer(token: String) {
        guard let url = URL(string: "你的PHP服务器接口地址") else { return }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = "token=\(token.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)".data(using: .utf8)
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        
        URLSession.shared.dataTask(with: request).resume()
    }
    
  2. 服务端统一处理Access Token与推送
    不管是Android还是iOS的推送请求,都由PHP服务器负责用ServiceAccount JSON获取Access Token,再调用FCM接口:

    <?php
    // 加载ServiceAccount JSON
    $serviceAccount = json_decode(file_get_contents('path/to/service-account.json'), true);
    $client = new Google_Client();
    $client->setAuthConfig($serviceAccount);
    $client->addScope('https://www.googleapis.com/auth/firebase.messaging');
    $client->fetchAccessTokenWithAssertion();
    $accessToken = $client->getAccessToken()['access_token'];
    
    // 构造FCM推送请求
    $fcmUrl = 'https://fcm.googleapis.com/v1/projects/你的Firebase项目ID/messages:send';
    $headers = [
        'Authorization: Bearer ' . $accessToken,
        'Content-Type: application/json'
    ];
    
    // 区分iOS和Android的推送配置
    $payload = [
        'message' => [
            'token' => '客户端传来的设备注册令牌',
            'notification' => [
                'title' => '测试通知',
                'body' => '来自FCM Http V1的推送'
            ],
            'apns' => [ // iOS专属配置
                'payload' => [
                    'aps' => [
                        'badge' => 1,
                        'sound' => 'default'
                    ]
                ]
            ]
        ]
    ];
    
    // 发送推送请求
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $fcmUrl);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    $response = curl_exec($ch);
    curl_close($ch);
    ?>
    

特殊场景:iOS端直接获取Access Token(不推荐)

如果因测试等特殊需求必须在iOS端用ServiceAccount获取Access Token,可使用Google的OAuth客户端库,但绝对不能将ServiceAccount JSON打包进正式版App:

import GoogleAPIClientForREST
import GTMSessionFetcher

func fetchFCMAccessToken() {
    guard let saFileUrl = Bundle.main.url(forResource: "service-account", withExtension: "json"),
          let saData = try? Data(contentsOf: saFileUrl) else {
        print("加载ServiceAccount文件失败")
        return
    }
    
    do {
        let credentials = try GTMServiceAccountCredentials(jsonData: saData)
        credentials.scopes = ["https://www.googleapis.com/auth/firebase.messaging"]
        
        let fetcherService = GTMFetcherService()
        fetcherService.authorizer = credentials
        
        let fetcher = fetcherService.fetcher(withURLString: "https://oauth2.googleapis.com/token")
        fetcher.setRequestValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        let params = [
            "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
            "assertion": credentials.jwt()
        ]
        fetcher.bodyString = params.map { "\($0.key)=\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)" }.joined(separator: "&")
        
        fetcher.beginFetch { data, error in
            if let error = error {
                print("获取Access Token失败: \(error)")
                return
            }
            guard let data = data,
                  let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
                  let token = json["access_token"] as? String else { return }
            print("获取到Access Token: \(token)")
            // 发送至PHP服务器
        }
    } catch {
        print("解析ServiceAccount失败: \(error)")
    }
}

关键注意事项

  • 安全优先级:ServiceAccount JSON是服务端专属文件,客户端存储会导致密钥泄露,强烈建议采用服务端统一处理的方案
  • iOS推送配置:需在Apple Developer后台配置APNs证书,并在Firebase控制台上传对应证书,否则推送会失败
  • Token有效期:Google Auth返回的Access Token有效期为1小时,服务端需缓存Token,到期后重新获取

内容的提问来源于stack exchange,提问作者waleed.makarem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 20:22:02