Android端通过FCM REST API向订阅主题发推送的问题求助
问题分析与解决方案
一、当前实现的核心问题
- 安全风险极高:直接在Android客户端嵌入FCM密钥/令牌,APK反编译后极易泄露,可能导致他人滥用你的FCM资源发送恶意通知。
- API版本不匹配:你混用了FCM旧API的请求格式与v1 API的端点,必然导致请求失败:
- 旧API(
fcm/send)采用to: "/topics/xxx"格式,授权头为key=遗留服务器密钥 - v1 API(
messages:send)要求完全不同的请求结构,且授权需OAuth2令牌,无法直接在客户端生成
- 旧API(
二、找不到服务器密钥的解决办法
Firebase控制台更新后,遗留服务器密钥的获取路径:
- 打开Firebase控制台进入你的项目
- 点击右上角设置图标 > 项目设置
- 切换到云消息传递标签页
- 拉至页面底部,找到遗留服务器密钥(注意:该密钥仅适用于旧版FCM API,不推荐生产环境使用)
若要使用更安全的FCM v1 API,无需服务器密钥,而是使用服务账号密钥生成OAuth2令牌,推荐配合Firebase Admin SDK使用(无需手动处理令牌)。
三、正确的实现方案
步骤1:搭建后端服务(必须)
所有FCM通知发送逻辑必须放在后端,以下是Node.js后端示例(基于Firebase Admin SDK):
const admin = require('firebase-admin'); const serviceAccount = require('./path-to-your-service-account-key.json'); admin.initializeApp({ credential: admin.credential.cert(serviceAccount) }); // 发送主题通知的核心逻辑 async function sendTopicNotification(title, body, topic) { const message = { notification: { title: title, body: body }, topic: topic }; try { const response = await admin.messaging().send(message); console.log('通知发送成功:', response); return { success: true, messageId: response }; } catch (error) { console.error('发送失败:', error); return { success: false, error: error.message }; } } // 用Express暴露接口供客户端调用 const express = require('express'); const app = express(); app.use(express.json()); app.post('/send-topic-notification', async (req, res) => { const { title, body, topic } = req.body; if (!title || !body || !topic) { return res.status(400).json({ error: '缺少必要参数' }); } const result = await sendTopicNotification(title, body, topic); res.json(result); }); app.listen(3000, () => { console.log('后端服务运行在3000端口'); });
步骤2:Android客户端修改逻辑
客户端不再直接调用FCM API,而是请求自建后端接口:
binding.btnSend.setOnClickListener { val messageContent = binding.edtNotification.text.toString().trim() if (messageContent.isEmpty()) { Toast.makeText(this, "请输入通知内容", Toast.LENGTH_SHORT).show() return@setOnClickListener } // 构建请求体 val requestBody = JSONObject().apply { put("title", "Padwa") put("body", messageContent) put("topic", "Notification") } // 替换为你的后端接口地址 val url = "https://your-backend-domain/send-topic-notification" val jsonObjectRequest = object : JsonObjectRequest( Request.Method.POST, url, requestBody, { response -> if (response.getBoolean("success")) { Log.d("TAG", "通知发送成功") Toast.makeText(this, "通知发送成功", Toast.LENGTH_SHORT).show() } else { Log.e("TAG", "发送失败: ${response.getString("error")}") Toast.makeText(this, "发送失败", Toast.LENGTH_SHORT).show() } }, { error -> Log.e("TAG", "请求失败: $error") Toast.makeText(this, "网络请求失败", Toast.LENGTH_SHORT).show() } ) { override fun getHeaders(): MutableMap<String, String> { val headers = HashMap<String, String>() headers["Content-Type"] = "application/json" // 若后端有身份验证,在此添加验证头(如管理员token) return headers } } Volley.newRequestQueue(this).add(jsonObjectRequest) }
步骤3:获取服务账号密钥
- Firebase控制台 > 项目设置 > 服务账号
- 点击生成新的私钥,下载JSON文件并放到后端项目中
四、临时调试方案(仅开发阶段使用)
若需快速验证功能,可暂时使用旧版FCM API:
- 将请求URL改回
https://fcm.googleapis.com/fcm/send - 修改授权头:
headers["Authorization"] = "key=你的遗留服务器密钥" - 保留原请求格式(
to: "/topics/Notification")
注意:此方案绝对不能用于生产环境,密钥泄露风险极高。
内容的提问来源于stack exchange,提问作者Pratiksha Shaha
相关产品推荐
相关产品推荐

