FCM服务端 - 谷歌云推送 - “errorCode“: “SENDER_ID_MISMATCH“-故障记录
前言
2020.10.27 我没解决,记录一些思路,这些问题其他人没有出现唯独我出现了,为什么。
2020.10.28 进度更新
2020.10.29 故障解决
核心代码
main
public class BaoTest {
private static final String String = null;
// 设备的token值
public static String token =
"AAAARWGhpmw:APA91bE6E7KCs-Tvjh5526LxGL-l1vYySzsnfNN8GhUpt9avG8bsjJEgqPX_txHBgHgfQ2BEt4Ms4aFRZGI7dbRm_pWy7JDqn1mIF9NYBU7WeLrOPt1j3GIll6njvxKkyp7r-q23hlh9";
// 渠道名字,也是APP的名字
// public static String appName = "VisionC";// 无关内容判别
// 主题名字
// public static String topic = "China";
// 通知消息题目
public static String title = "tip";
// 通知消息内容
public static String body = "body";
// 测试内容
public static void main(String args[]) throws Exception {
// 添加tokens
List<String> tokens = new LinkedList();
tokens.add(token);
// 设置Java代理,端口号是代理软件开放的端口,这个很重要。
System.setProperty("proxyHost", "localhost");
System.setProperty("proxyPort", "8580"); // 1080,8580
// String jsonPath = "path/to/serviceAccountKey.json" ;
// String dataUrl = "https://telecomm-773e6e.firebaseio.com/";
// String jsonPath = "D:\\eclipse_workplace_learn\\HelloSpringMVC\\src\\main\\resources\\"
// //+ "baotestserver-firebase-adminsdk-whfsf-311cd00bee"
// +"visionc-cd27f-firebase-adminsdk-rdqff-26955b119f"
// + ".json" ;
// 初始化FirebaseApp
if (!FireBaseUtil.initSDK())
return;
System.out.println("FireBaseUtil init over");
FireBaseUtil.pushSingle(token, title, body); // 单推
//
//
// FireBaseUtil.registrationTopic(appName, tokens, topic); //设置主题
//
// FireBaseUtil.sendTopicMes(appName, topic, title, body); //按主题推送
//
// FireBaseUtil.cancelTopic(appName, tokens, topic); //取消主题
//
// 安卓设备推送
// FireBaseUtil.pushSingleToAndroid(appName, token, title, body);
// FireBaseUtil.registrationTopic(appName, tokens, topic); //设置主题
// FireBaseUtil.sendTopicMesToAndroid(appName, topic, title, body);
}
}
FireBaseUtil
工具类
package com.bao;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.annotations.VisibleForTesting;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.UserRecord;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.messaging.*;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
* {@code description: FireBaseUtil Firebase Cloud Message}
* Google_FireBase推送工具类
*
* @author zhangkai
* @version 1.0
* @since 2019/7/5 15:48
*/
@Component
public class FireBaseUtil {
//获取AndroidConfig.Builder对象
private static AndroidConfig.Builder androidConfigBuilder = AndroidConfig.builder();
//获取AndroidNotification.Builder对象
private static AndroidNotification.Builder androidNotifiBuilder = AndroidNotification.builder();
public static FirebaseApp firebaseApp;
// Retrieve services by passing the defaultApp variable...
public static FirebaseAuth defaultAuth ;
public static FirebaseDatabase defaultDatabase ;
public static boolean initSDK() {
try {
//设置环境
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.setDatabaseUrl("https://visionc-cd27f.firebaseio.com")
.build();
firebaseApp = FirebaseApp.initializeApp(options);
// Retrieve services by passing the defaultApp variable...
defaultAuth = FirebaseAuth.getInstance(firebaseApp); //not used
defaultDatabase = FirebaseDatabase.getInstance(firebaseApp); //not used
System.out.println(firebaseApp.getName()); // "[DEFAULT]"
} catch (Exception e) {
System.out.println("SDK init failed");
e.printStackTrace();
return false;
}
return true;
}
/**
* 单设备推送
* @param token 注册token
* @param title 推送题目
* @param body 推送内容
* @return
* @throws IOException
* @throws FirebaseMessagingException
*/
public static void pushSingle( String token, String title, String body) throws IOException, FirebaseMessagingException {
//获取实例
// FirebaseApp firebaseApp = FirebaseApp.getInstance();
//实例为空的情况
if (firebaseApp == null) {
System.out.println("实例为空");
return;
}
//构建消息内容
//Notification(String title, String body, String imageUrl)
Message message = Message.builder().setNotification(new Notification(title, body))
.setToken(token)
.build();
//发送后,返回messageID
String response = FirebaseMessaging.getInstance(firebaseApp).send(message);
System.out.println("单个设备推送成功 : " + response);
}
/**
* 单设备推送
* @param token 注册token
* @param title 推送题目
* @param body 推送内容
* @param map 额外的参数
* @return
* @throws IOException
* @throws FirebaseMessagingException
*/
public static void pushSingle(String token, String title, String body, Map<String,String> map) throws IOException, FirebaseMessagingException {
//获取实例
FirebaseApp firebaseApp = FirebaseApp.getInstance();
//实例为空的情况
if (firebaseApp == null) {
return;
}
//构建消息内容
//Notification(String title, String body, String imageUrl)
Message message = Message.builder().setNotification(new Notification(title, body))
.putAllData(map)
.setToken(token)
.build();
//发送后,返回messageID
//message.getNotification();
String response = FirebaseMessaging.getInstance(firebaseApp).send(message);
System.out.println("单个设备推送成功 : " + response);
}
/**
* {@code description: 单设备推送}
* @param token 注册token
* @param title 推送题目
* @param body 推送内容
* @param extra 额外的参数
* @return
* @throws IOException
* @throws FirebaseMessagingException
* @author zhangkai
* @since 2019/10/14 11:24
*/
public static void pushSingle( String token, String title, String body, String extra) throws IOException, FirebaseMessagingException {
//获取实例
FirebaseApp firebaseApp = FirebaseApp.getInstance();
//实例为空的情况
if (firebaseApp == null) {
return;
}
//构建消息内容
//Notification(String title, String body, String imageUrl)
Message message = Message.builder().setNotification(new Notification(title, body))
.putData("extra",extra)
.setToken(token)
.build();
//发送后,返回messageID
String response = FirebaseMessaging.getInstance(firebaseApp).send(message);
System.out.println("单个设备推送成功 : " + response);
}
/**
* 给设备订阅主题
* @param tokens 设备的token,最大1000个
* @param topic 要添加的主题
* @return
* @throws FirebaseMessagingException
* @throws IOException
*/
public static void registrationTopic( List<String> tokens, String topic) throws FirebaseMessagingException, IOException {
//获取实例
FirebaseApp firebaseApp = FirebaseApp.getInstance();
//实例不存在的情况
if (firebaseApp == null) {
return;
}
//订阅,返回主题管理结果对象。
TopicManagementResponse response = FirebaseMessaging.getInstance(firebaseApp).subscribeToTopic(tokens, topic);
System.out.println("添加设备主题,成功:" + response.getSuccessCount() + ",失败:" + response.getFailureCount());
}
/**
* 取消设备的订阅主题
* @param tokens 设备的token,最大1000个
* @param topic 取消的主题
* @return
* @throws FirebaseMessagingException
* @throws IOException
*/
public static void cancelTopic( List<String> tokens, String topic) throws FirebaseMessagingException, IOException {
//获取实例
FirebaseApp firebaseApp = FirebaseApp.getInstance();
//实例不存在的情况
if (firebaseApp == null) {
return;
}
//取消订阅,返回主题管理结果对象。
TopicManagementResponse response = FirebaseMessaging.getInstance(firebaseApp).unsubscribeFromTopic(tokens, topic);
System.out.println("取消设备主题,成功:" + response.getSuccessCount() + ",失败:" + response.getFailureCount());
}
/**
* 按主题推送
* @param topic 主题的名字
* @param title 消息题目
* @param body 消息体
* @return
* @throws FirebaseMessagingException
* @throws IOException
*/
public static void sendTopicMes( String topic, String title, String body) throws FirebaseMessagingException, IOException {
//获取实例
FirebaseApp firebaseApp = FirebaseApp.getInstance();
//实例不存在的情况
if (firebaseApp == null) {
return;
}
//构建消息
Message message = Message.builder()
.setNotification(new Notification(title, body))
.setTopic(topic)
.build();
//发送后,返回messageID
String response = FirebaseMessaging.getInstance(firebaseApp).send(message);
System.out.println("主题推送成功: " + response);
}
/**
* 单条Android设备推送消息(和pushSingle方法几乎没有区别)
* @param token 注册token
* @param title 推送题目
* @param body 推送内容
* @throws FirebaseMessagingException
*/
public static void pushSingleToAndroid( String token, String title, String body) throws FirebaseMessagingException {
//获取实例
FirebaseApp firebaseApp = FirebaseApp.getInstance();
//实例为空的情况
if (firebaseApp == null) {
return;
}
androidConfigBuilder.setRestrictedPackageName("io.telecomm.telecomm");
androidNotifiBuilder.setColor("#55BEB7");// 设置消息通知颜色
androidNotifiBuilder.setIcon("https://www.shiku.co/images/favicon.png");// 设置消息图标
androidNotifiBuilder.setTitle(title);// 设置消息标题
androidNotifiBuilder.setBody(body);// 设置消息内容
AndroidNotification androidNotification = androidNotifiBuilder.build();
androidConfigBuilder.setNotification(androidNotification);
AndroidConfig androidConfig = androidConfigBuilder.build();
//构建消息
Message message = Message.builder()
.setToken(token)
.setAndroidConfig(androidConfig)
.build();
//发送后,返回messageID
String response = FirebaseMessaging.getInstance(firebaseApp).send(message);
System.out.println("单个安卓设备推送成功 : " + response);
}
/**
* Android按主题推送(和sendTopicMes方法几乎没有区别)
* @param topic 主题的名字
* @param title 消息题目
* @param body 消息体
* @return
* @throws FirebaseMessagingException
* @throws IOException
*/
public static void sendTopicMesToAndroid( String topic, String title, String body) throws FirebaseMessagingException, IOException {
//获取实例
FirebaseApp firebaseApp = FirebaseApp.getInstance();
//实例为空的情况
if (firebaseApp == null) {
return;
}
androidNotifiBuilder.setColor("#55BEB7");// 设置消息通知颜色
androidNotifiBuilder.setIcon("https://www.shiku.co/images/favicon.png");// 设置消息图标
androidNotifiBuilder.setTitle(title);// 设置消息标题
androidNotifiBuilder.setBody(body);// 设置消息内容
AndroidNotification androidNotification = androidNotifiBuilder.build();
androidConfigBuilder.setNotification(androidNotification);
AndroidConfig androidConfig = androidConfigBuilder.build();
//构建消息
Message message = Message.builder()
.setTopic(topic)
.setAndroidConfig(androidConfig)
.build();
String response = FirebaseMessaging.getInstance(firebaseApp).send(message);
System.out.println("安卓主题推送成功: " + response);
}
}
接入流程
根据官方文档整理出的服务器接入流程
admin-sdk接入
采用maven接入
完毕后用maven update和download
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>6.8.1</version>
</dependency>
service-json接入
-首先,获取项目的json。
通过项目的project setting里的 Service accounts 里的下载选项,获取密匙,json文件,
-其次设置项目的json的环境变量。
window系统可以命令行,其中path为你存放json的绝对路径
set GOOGLE_APPLICATION_CREDENTIALS=PATH
然后官方表示:
完成上述步骤后,应用默认凭据 (ADC) 能够隐式确定您的凭据,如此您便能在非 Google 环境中测试或运行时使用服务帐号凭据。
初始化sdk
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.setDatabaseUrl("https://<DATABASE_NAME>.firebaseio.com/")
.build();
FirebaseApp.initializeApp(options);
获取应用(单个)
因为我测试功能1个就好,有其他需求看官方文档,写的很详细
// Initialize the default app
FirebaseApp defaultApp = FirebaseApp.initializeApp(defaultOptions);
System.out.println(defaultApp.getName()); // "[DEFAULT]"
// Retrieve services by passing the defaultApp variable...
FirebaseAuth defaultAuth = FirebaseAuth.getInstance(defaultApp);
FirebaseDatabase defaultDatabase = FirebaseDatabase.getInstance(defaultApp);
// ... or use the equivalent shorthand notation
defaultAuth = FirebaseAuth.getInstance();
defaultDatabase = FirebaseDatabase.getInstance();
测试
运行我的main开始测试,报错,结果如下:
[DEFAULT]
FireBaseUtil init over
Exception in thread "main" com.google.firebase.messaging.FirebaseMessagingException: SenderId mismatch
at com.google.firebase.messaging.FirebaseMessagingClientImpl.newException(FirebaseMessagingClientImpl.java:306)
at com.google.firebase.messaging.FirebaseMessagingClientImpl.createExceptionFromResponse(FirebaseMessagingClientImpl.java:211)
at com.google.firebase.messaging.FirebaseMessagingClientImpl.send(FirebaseMessagingClientImpl.java:127)
at com.google.firebase.messaging.FirebaseMessaging$1.execute(FirebaseMessaging.java:139)
at com.google.firebase.messaging.FirebaseMessaging$1.execute(FirebaseMessaging.java:136)
at com.google.firebase.internal.CallableOperation.call(CallableOperation.java:36)
at com.google.firebase.messaging.FirebaseMessaging.send(FirebaseMessaging.java:106)
at com.google.firebase.messaging.FirebaseMessaging.send(FirebaseMessaging.java:90)
at com.bao.FireBaseUtil.pushSingle(FireBaseUtil.java:86)
at com.bao.BaoTest.main(BaoTest.java:48)
Caused by: com.google.api.client.http.HttpResponseException: 403 Forbidden
{
"error": {
"code": 403,
"message": "SenderId mismatch",
"status": "PERMISSION_DENIED",
"details": [
{
"@type": "type.googleapis.com/google.firebase.fcm.v1.FcmError",
"errorCode": "SENDER_ID_MISMATCH"
}
]
}
}
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1072)
at com.google.firebase.messaging.FirebaseMessagingClientImpl.sendSingleRequest(FirebaseMessagingClientImpl.java:153)
at com.google.firebase.messaging.FirebaseMessagingClientImpl.send(FirebaseMessagingClientImpl.java:125)
... 7 more
结语
我所有操作严格按着官网文档进行,并且也进行了翻墙处理,所采用token在控制台可以进行发送(10.28,发现控制台测试手法错误,10.29 发现token错误,故障解决),我不认为我操作有误。
我的操作指引参考:
http://www.mamicode.com/info-detail-2682997.html
目前钻研了2天,后续有进度我会更新。
2020.10.28
首先从第15段话研究,这个思考的切入点是,发送者id不匹配,这里的问题在于我的发送着id是什么, (HTTP错误代码= 403)经过身份验证的发件人ID与注册令牌的发件人ID不同。在官方原文中这么认为的。
从字面上分析得出,经过身份验证的发件人ID,那么可以认为身份验证成功了吗,,不可以,因为当我修改了json文件后,并且保存后,在实质上并没有发生什么变化依然是这个数值
于是我开始试探,摸索这个json文件的字段的判别方案
判别依据为寻找变化后影响故障类型的字段
type,内容和长度不可变
project_id ,内容和长度不可变
private_key_id,内容和长度可变
private_key,内容和长度不可变
client_email,,内容和长度不可变
client_id,内容和长度可变
auth_uri,auth_provider_x509_cert_url,client_x509_cert_url
,内容和长度可变
token_uri,内容和长度不可变
这能说明说明什么?
说明json文件不可自证内部数据正确,也就是不能确保内容完整,
但是对于字段的删除引起了错误,说明json文件格式完整
对于新字段的添加没有失败,这符合json的基础格式,在此我想要添加senderid进行测试,存在可能这部分内容的default判定
猜想无效,由于对于json的更改,因此重新生成一组
13.11 重新配置了token和json理论上没有可操作空间了,对此需要分析和明确,senderid
14.06 没有找到任何信息,我当前的处境其实也可以被认为是走了捷径,但是存在未知的因素走不通,我使用着官方sdk
14.16 对此我找到了一个解决方案,我尝试进行其他需要权限的操作,通过这些操作来反向证明我是否有操作权限,如果我拥有操作权限也就是json文件应用正确,那么我就可以完整定义错误必然和token有关,如果我没有操作权限,也就是和token无关的操作失败了,我就可以先解决操作权限问题,这是一个好办法
但是这个思路有缺陷,这个思路的最大问题就是目前我没找到联网操作。
Successfully created new user: d65DHPSk1agrE3MVy70LaIMe73p1
14.57 非常好经过验证,我证明json没有问题,那么假设token也没有问题,那问题在哪呢?
14.59 还是失败,至此我可以排除和json相关的内容,明确故障来自token和senderid
15.02 优先尝试rebuild,因为见效快
15.07 rebuild无效,现在我进一步思考我的测试函数和正统运行环境间的区别,其中有一个重点,那就是我目前不是服务器模式,我目前是java文件调试模式
15.44 我运行起来了服务器,然后呢,我该怎么办?
16.07 我刚刚想到是否能去证明json的对或者错误,但是对于这些字段很可能是不被使用的,并且理论上这没有确定的价值
16.44 理论上之前的用户创建操作应该是证明了json的正确性,因此可以认为成功,理论上整体管理页面里只有一个token,因此不存在token错误的可能性
16.58 发现问题FCM官网的token测试工具不合理,即使值是错误的也可以进行推送,似乎仅要求数据格式正确
17.00 也不要求数据格式正确。因此无法确信参数正确
2020.10.29
结论 token错误
经过验证,前端开发人员提供给我的token数值错误,并且我所拥有的测试指引说明不完善,导致了误解,因此无法第一时间判断问题。
思考路径和解决方案
token测试指引
首先,token必须是注册token,registration token
其他任何值都不行,最有效的验证方法如下:

在这个界面里输入token并且点击(test)如果你的设备能够收获一条推送,那么就证明这个token是正确的,在目前的官网提供的测试方法里,这也是唯一的测试设备注册令牌的方式(注意这个token的名字为注册令牌)
错误的验证方式
这种验证方法是错误的,不要使用这个途径

在推送测试界面中的这个选项里只能选择,全部用户设备,主题推送,没有单独用户推送,因此这里不能验证token的正确。
官方的获取注册令牌的操作指引链接如下
https://firebase.google.com/docs/cloud-messaging/android/first-message?authuser=0#retrieve-the-current-registration-token
错误的token来源
从这里获取的token是错误的,请从android端获取

这里是控制台唯一能够获取token的地方,而当初前端人员给我的也是这里的值,但是需要注意,这里的值虽然也被称为token(令牌)但是不是注册令牌,而是服务令牌,用这个值进行推送测试,就会获得403.
json验证方案
如果用官方的sdk进行环境搭建,那么想要尝试单推最少只要2个参数,1是权限json文件,2是目标token,而我之前一直错误通过不断收缩可能范围最终我需要在告诉其他同事你有问题前完整的证明我没有问题:
因此我需要验证json权限文件使用正确,对此我采用方法为:
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.setServiceAccountId(client_email)
.build();
firebaseApp = FirebaseApp.initializeApp(options);
// Retrieve services by passing the defaultApp variable...
defaultAuth = FirebaseAuth.getInstance(firebaseApp); //not used
//defaultDatabase = FirebaseDatabase.getInstance(firebaseApp); //not used
System.out.println(firebaseApp.getName()); // "[DEFAULT]"
CreateRequest request = new CreateRequest()
.setEmail("user2@example.com")
.setEmailVerified(false)
.setPassword("secretPassword")
.setPhoneNumber("+11234567892")
.setDisplayName("John Doe")
.setPhotoUrl("http://www.example.com/12345678/photo.png")
.setDisabled(false);
UserRecord userRecord = FirebaseAuth.getInstance().createUser(request);
System.out.println("Successfully created new user: " + userRecord.getUid());
client_email 从json文件里复制。
如果这段函数能够被顺利执行,并且反馈给你注册完毕的用户id,那么就可以证明你下载的权限json文件是正确的。剩下的错误是其他因素
错误可能因素
虽然我最终排查出来了结论是token错误,但是在研究过程中我还是积累一些其他的可能性,我查阅大量资料可能性如下:
8.42 首先走出国门,更换搜索引擎
8.49 在第一篇文章里提出了解决方案,升级sdk版本到9.6.1
8.59 第二篇文章里提到了senderid,token,json,并且说明不能在FCM中使用GCM创建的设备ID,也就是目标物ID,我认为是token
9.02 另一个段话描述密匙需要配合发送者ID使用
9.06 第四段话描述要求项目和服务器密匙搭配
9.14 第五段话描述的含义是要求rebuild项目(+1)
9.20 第六段话的含义为确保token生成在正确的环境
9.22 第七段话的含义为senderid和控制台中的数据不符时,请检查google-services.json
9.25 第八段话的确保服务器key值,对比新旧版本可以得出跟权限json有关
9.27 第九段话的含义为GCM和FCM版本冲突
9.42 第十段话的含义为令牌错误,选用了其他程序的令牌
9.45 第十一段话的含义为在json文件中确保senderID,发送者id的错误和json权限有关
9.50 第十二段话的含义为初始化函数可能有问题(json?)
9.54 第十三段话的含义为使用完整的token
9.57 第十四段话的含义为values.xml没有更新,需要手动更新?rebuild?
10.10 第十五段话,重点描述了发送者id不匹配的可能性,当进行发送时可能会自动附加一个id而这个id和设定id不同,或者是用户注册时绑定的id发生了变化,导致新发送者id无效
10.14 第十六段话则描述了获取token的另一个方案,记录网址:
https://medium.com/1mgofficial/migrating-from-gcm-to-fcm-with-different-sender-ids-for-the-same-application-4fc36d97568e
10.32 上一段是senderid不匹配的可能性,这一段是403的思考路径
10.39 第一段话表明,注意将发送内容转为string格式,以对应最新的协议http v1协议
10.43 第二段话表明需要为止启动和准备GoogleAPI
以上每一段话都是遇到类似问题时,其他开发人员的解决方案,几乎是全网解决方案了。而与我完全相同的错误,全网没有明面上的解决方案,唯一的一篇几年前的帖子也没有人回复。
对此我只想说,
再小的错误都有可能有人犯,信息差,沟通,以及巧合,那些常识之外的错误往往会成为无法解决的bug,因此任何人,都应该积极的记录和分享自身遇到的困难和解决方案,这样技术才有可能进步
虽然我这次的bug很简单,但是如果前端一直认为他给我的值是对的,我也就永远无法解决错误,因此我解决本次错误的参考价值为,我排除了一切其他的可能要素
希望这些解决方案,能够对你有效,谢谢。(✪ω✪)
本文记录了在使用FCM服务端进行谷歌云推送时遇到的'errorCode': 'SENDER_ID_MISMATCH'问题的解决过程。通过分析json文件、token验证和错误排查,最终发现是前端提供的token数值错误导致。文章强调了正确验证token和理解FCM工作原理的重要性。
1521

被折叠的 条评论
为什么被折叠?



