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

如何从Android应用直接向浏览器发送FCM消息?

安全实现Android应用向指定浏览器设备发送FCM消息

问题背景

我已经完成了以下配置:

  • 通过Firebase Hosting部署了Angular构建的网站,集成Firebase Messaging后,用CURL发送FCM消息能在浏览器正常显示
  • Android应用集成了Firebase Messaging,可以接收网站发送的消息

现在需要实现从Android应用直接向指定浏览器设备发送FCM消息,但遇到了两个问题:

  1. Android端的FirebaseMessaging和RemoteMessage API仅支持全局sender_id,没有CURL/HTTP请求中的to参数,无法指定单个设备
  2. 用HTTP POST请求虽然能实现,但需要在Android应用中存储Server-API-Key,存在被反编译泄露的安全风险

测试代码参考

浏览器接收的CURL调用:

curl -H "Content-type: application/json" -H "Authorization:key=<API-KEY>" -X POST -d "{\"data\": { \"score\": \"5x1\",\"time\": \"15:10\"},\"to\" : \"<token from the browser>\"}" https://fcm.googleapis.com/fcm/send

Android应用内测试用HTTP调用:

try { 
    URL url = new URL("https://fcm.googleapis.com/fcm/send"); 
    HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 
    urlConnection.setRequestMethod("POST"); 
    urlConnection.setDoOutput(true); 
    urlConnection.setRequestProperty("Content-Type", "application/json"); 
    urlConnection.setRequestProperty("Authorization", "key=<API-KEY>"); 
    String postJsonData = "{\"to\": \"<token from the browser>\", \"data\": {\"data\": \"" + data.getNote() + "\"}}"; 
    urlConnection.setDoOutput(true); 
    DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream()); 
    wr.writeBytes(postJsonData); 
    wr.flush(); 
    wr.close(); 
    int responseCode = urlConnection.getResponseCode(); 
    Log.i(TAG, "POST Response Code :: " + responseCode); 
    if (responseCode == HttpURLConnection.HTTP_OK){ 
        Log.i(TAG, "succeeded"); 
    } 
} catch(MalformedURLException e) { 
    Log.e(TAG, "MalformedURLException" , e); 
} catch (IOException e) { 
    Log.e(TAG, "IOException" , e); 
}

安全解决方案

方案1:使用Firebase Cloud Functions作为中间层(推荐)

这是最安全的实现方式,因为Server-API-Key只会存储在云端的Cloud Functions中,不会暴露给Android客户端。具体步骤:

  1. 创建一个Callable类型的Cloud Function,负责接收Android端传来的浏览器设备Token和消息内容,然后调用Firebase Admin SDK发送FCM消息
  2. Android应用只需要调用这个Cloud Function,不需要接触任何敏感密钥
  3. 可以给Cloud Function添加身份验证(比如验证请求来自已登录的Firebase用户),进一步防止滥用
Cloud Function示例代码(JavaScript):
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

// 定义Callable函数,用于向指定浏览器设备发送FCM消息
exports.sendFcmToBrowser = functions.https.onCall(async (data, context) => {
    // 可选:验证请求来自已认证用户
    if (!context.auth) {
        throw new functions.https.HttpsError(
            "unauthenticated",
            "需要先登录才能发送消息"
        );
    }

    const { browserToken, messageData } = data;
    const fcmMessage = {
        data: messageData,
        token: browserToken
    };

    try {
        const sendResult = await admin.messaging().send(fcmMessage);
        return { success: true, messageId: sendResult };
    } catch (error) {
        throw new functions.https.HttpsError("internal", "发送消息失败", error);
    }
});
Android端调用Callable Function的代码:
// 初始化Firebase Functions
FirebaseFunctions firebaseFunctions = FirebaseFunctions.getInstance();

// 准备要传递的数据:浏览器Token和消息内容
Map<String, Object> requestData = new HashMap<>();
requestData.put("browserToken", "<浏览器设备的FCM Token>");
requestData.put("messageData", new HashMap<String, String>() {{
    put("score", "5x1");
    put("time", "15:10");
}});

// 调用Cloud Function
firebaseFunctions.getHttpsCallable("sendFcmToBrowser")
        .call(requestData)
        .addOnSuccessListener(result -> {
            Log.i(TAG, "消息发送成功,返回结果:" + result.getData());
        })
        .addOnFailureListener(e -> {
            Log.e(TAG, "消息发送失败", e);
        });

方案2:使用主题订阅(适合批量场景)

如果你的需求是向一组浏览器设备发送消息,而非单个指定设备,可以采用主题订阅的方式:

  • 浏览器端:调用messaging.subscribeToTopic("your_topic_name")订阅指定主题
  • Android端:使用FirebaseMessaging API直接向主题发送消息,不需要任何敏感密钥
Android端主题消息发送代码:
// 构建消息内容
RemoteMessage remoteMessage = new RemoteMessage.Builder("<你的Sender ID>@fcm.googleapis.com")
        .setMessageId(String.valueOf(new Random().nextInt(999999)))
        .addData("score", "5x1")
        .addData("time", "15:10")
        .setTopic("your_topic_name") // 指定要发送的主题
        .build();

// 发送消息
FirebaseMessaging.getInstance().send(remoteMessage)
        .addOnSuccessListener(unused -> Log.i(TAG, "主题消息发送成功"))
        .addOnFailureListener(e -> Log.e(TAG, "主题消息发送失败", e));

这个方案的优势是完全不需要暴露密钥,但局限是只能向订阅主题的所有设备群发消息,无法指定单个设备。


为什么不能在Android端存储Server-API-Key?

  • Android APK很容易被反编译,一旦密钥泄露,攻击者可以用你的密钥发送任意FCM消息,甚至滥用你的Firebase资源
  • 这违反了Firebase的安全最佳实践,Server-API-Key属于敏感凭证,应该只在受信任的后端环境(比如Cloud Functions、你的服务器)中使用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:56:52