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端正确操作步骤
获取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() }服务端统一处理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
相关产品推荐
相关产品推荐

