C2DM 推送通知 导致 401 未授权错误的原因是什么?
更新
已解决 - 感谢@MusiGenesis 的坚持,我通过注册新的 Google 邮件帐户和新的 C2DM 帐户解决了问题。更新网络服务器中的相关凭据后,Android 应用程序都开始像魔术一样工作。
更新结束
我正在寻找发送推送通知时出现 401 未经授权错误的原因的明确列表,以便我可以尝试消除我的问题。
我有一个在 C2DM 上注册的 google 电子邮件 我可以使用curl来获取授权码
我从我的android应用程序上的注册用户那里获得了身份验证令牌
使用2个身份验证令牌(刷新)从我的网络服务器发送推送通知请求时,我从我的网络服务器收到401未经授权的错误。
据我所知,我正在做我需要做的一切,所以我正在寻找我可能缺少的东西。我在网上查了一下,似乎很多人都有同样的问题,但没有明确的答案。 任何帮助都非常感谢
更新
正如下面的答案中提到的,似乎需要第二阶段来获取注册ID,该ID似乎与Android应用程序上注册用户收到的身份验证令牌不同。查看了 Jumpnote 代码和这两个资源
和
http://marakana.com/forums/android/general/272.html
我明白了没有关于第二次注册调用以获取除身份验证令牌之外的注册 ID 的信息。我显然错过了一些东西,如果有人能为我拼写出来,我将不胜感激。
** 更新 2 **
我的 C2DM 接收器看起来像
public class C2DMReceiver extends C2DMBaseReceiver {
public C2DMReceiver() {
super(REGISTERED_GOOGLE_MAIL_ADDRESS);
}
@Override
public void onRegistered(Context context, String registrationId)
throws java.io.IOException {
// The registrationId should be sent to your application server.
Log.e("C2DM", "Registration ID arrived!");
Log.e("C2DM", registrationId);
Intent webSeverReg = new Intent(this, RegService.class);
startService(webServerReg);
};
@Override
protected void onMessage(Context context, Intent intent) {
Log.e("C2DM", "Message: Fantastic!!!");
// Extract the payload from the message
Bundle extras = intent.getExtras();
if (extras != null) {
System.out.println(extras.get("payload"));
// Now do something smart based on the information
}
}
@Override
public void onError(Context context, String errorId) {
Log.e("C2DM", "Error occured!!!");
}
}
从 Jumpnote 应用程序中获取的 C2DMBaseReceiver 看起来像这样
/**
* Base class for C2D message receiver. Includes constants for the
* strings used in the protocol.
*/
public abstract class C2DMBaseReceiver extends IntentService {
private static final String C2DM_RETRY = "com.google.android.c2dm.intent.RETRY";
public static final String REGISTRATION_CALLBACK_INTENT = "com.google.android.c2dm.intent.REGISTRATION";
private static final String C2DM_INTENT = "com.google.android.c2dm.intent.RECEIVE";
// Logging tag
private static final String TAG = "C2DM";
// Extras in the registration callback intents.
public static final String EXTRA_UNREGISTERED = "unregistered";
public static final String EXTRA_ERROR = "error";
public static final String EXTRA_REGISTRATION_ID = "registration_id";
public static final String ERR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
public static final String ERR_ACCOUNT_MISSING = "ACCOUNT_MISSING";
public static final String ERR_AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED";
public static final String ERR_TOO_MANY_REGISTRATIONS = "TOO_MANY_REGISTRATIONS";
public static final String ERR_INVALID_PARAMETERS = "INVALID_PARAMETERS";
public static final String ERR_INVALID_SENDER = "INVALID_SENDER";
public static final String ERR_PHONE_REGISTRATION_ERROR = "PHONE_REGISTRATION_ERROR";
// wakelock
private static final String WAKELOCK_KEY = "C2DM_LIB";
private static PowerManager.WakeLock mWakeLock;
private final String senderId;
/**
* The C2DMReceiver class must create a no-arg constructor and pass the
* sender id to be used for registration.
*/
public C2DMBaseReceiver(String senderId) {
// senderId is used as base name for threads, etc.
super(senderId);
this.senderId = senderId;
}
/**
* Called when a cloud message has been received.
*/
protected abstract void onMessage(Context context, Intent intent);
/**
* Called on registration error. Override to provide better
* error messages.
*
* This is called in the context of a Service - no dialog or UI.
*/
public abstract void onError(Context context, String errorId);
/**
* Called when a registration token has been received.
*/
public void onRegistered(Context context, String registrationId) throws IOException {
// registrationId will also be saved
}
/**
* Called when the device has been unregistered.
*/
public void onUnregistered(Context context) {
}
@Override
public final void onHandleIntent(Intent intent) {
Log.d(TAG, "@@@@ - onHandleIntent Messaging request received");
try {
Context context = getApplicationContext();
if (intent.getAction().equals(REGISTRATION_CALLBACK_INTENT)) {
handleRegistration(context, intent);
} else if (intent.getAction().equals(C2DM_INTENT)) {
onMessage(context, intent);
} else if (intent.getAction().equals(C2DM_RETRY)) {
C2DMessaging.register(context, senderId);
}
} finally {
// Release the power lock, so phone can get back to sleep.
// The lock is reference counted by default, so multiple
// messages are ok.
// If the onMessage() needs to spawn a thread or do something else,
// it should use it's own lock.
mWakeLock.release();
}
}
/**
* Called from the broadcast receiver.
* Will process the received intent, call handleMessage(), registered(), etc.
* in background threads, with a wake lock, while keeping the service
* alive.
*/
static void runIntentInService(Context context, Intent intent) {
if (mWakeLock == null) {
// This is called from BroadcastReceiver, there is no init.
PowerManager pm =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
WAKELOCK_KEY);
}
mWakeLock.acquire();
// Use a naming convention, similar with how permissions and intents are
// used. Alternatives are introspection or an ugly use of statics.
String receiver = context.getPackageName() + ".C2DMReceiver";
intent.setClassName(context, receiver);
context.startService(intent);
}
private void handleRegistration(final Context context, Intent intent) {
final String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID);
Log.d(TAG, "@@@@ - HandleRegistration Messaging request received");
String error = intent.getStringExtra(EXTRA_ERROR);
String removed = intent.getStringExtra(EXTRA_UNREGISTERED);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "dmControl: registrationId = " + registrationId +
", error = " + error + ", removed = " + removed);
}
if (removed != null) {
// Remember we are unregistered
C2DMessaging.clearRegistrationId(context);
onUnregistered(context);
return;
} else if (error != null) {
// we are not registered, can try again
C2DMessaging.clearRegistrationId(context);
// Registration failed
Log.e(TAG, "Registration error " + error);
onError(context, error);
if ("SERVICE_NOT_AVAILABLE".equals(error)) {
long backoffTimeMs = C2DMessaging.getBackoff(context);
Log.d(TAG, "Scheduling registration retry, backoff = " + backoffTimeMs);
Intent retryIntent = new Intent(C2DM_RETRY);
PendingIntent retryPIntent = PendingIntent.getBroadcast(context,
0 /*requestCode*/, retryIntent, 0 /*flags*/);
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.ELAPSED_REALTIME,
backoffTimeMs, retryPIntent);
// Next retry should wait longer.
backoffTimeMs *= 2;
C2DMessaging.setBackoff(context, backoffTimeMs);
}
} else {
try {
onRegistered(context, registrationId);
C2DMessaging.setRegistrationId(context, registrationId);
} catch (IOException ex) {
Log.e(TAG, "Registration error " + ex.getMessage());
}
}
}
}
UPDATE
SOLVED - Thank's to @MusiGenesis persistence with this I solved the problem by registering a new Google mail account and a new C2DM account. After updating the relevant credentials in the web server and the android app all started working like magic.
END OF UPDATE
I'm looking for a definitive list for causes of 401 unauthorised errors when sending a push notification so I can try to eliminate my problem.
I have a google email registered with C2DM
I can use curl to get an authorisation code
I have the auth token from the registered user on my android app
Using the 2 auth tokens (refreshed) I get 401 unauthorised errors from my web server when sending a push notification request from my web server.
As far as I can tell I am doing everything I need to do so I'm looking for what I may be missing. I have scoured the internet and lots of people seem to be having the same problem with no definite answer.
Any help greatly appreciated
UPDATE
As mentioned in the answer below it seems there is a second stage required to get a registration ID which appears to be different from the auth token received by the registered user on the android app. Having looked at the jumpnote code and these two resources
and
http://marakana.com/forums/android/general/272.html
I see no info regarding a second registration call to get a regisration ID in addition to the auth token. I'm obviously missing something and would be gratefull if someone could spell this out for me.
** UPDATE 2 **
My C2DM Receiver looks like this
public class C2DMReceiver extends C2DMBaseReceiver {
public C2DMReceiver() {
super(REGISTERED_GOOGLE_MAIL_ADDRESS);
}
@Override
public void onRegistered(Context context, String registrationId)
throws java.io.IOException {
// The registrationId should be sent to your application server.
Log.e("C2DM", "Registration ID arrived!");
Log.e("C2DM", registrationId);
Intent webSeverReg = new Intent(this, RegService.class);
startService(webServerReg);
};
@Override
protected void onMessage(Context context, Intent intent) {
Log.e("C2DM", "Message: Fantastic!!!");
// Extract the payload from the message
Bundle extras = intent.getExtras();
if (extras != null) {
System.out.println(extras.get("payload"));
// Now do something smart based on the information
}
}
@Override
public void onError(Context context, String errorId) {
Log.e("C2DM", "Error occured!!!");
}
}
C2DMBaseReceiver taken from jumpnote app looks like this
/**
* Base class for C2D message receiver. Includes constants for the
* strings used in the protocol.
*/
public abstract class C2DMBaseReceiver extends IntentService {
private static final String C2DM_RETRY = "com.google.android.c2dm.intent.RETRY";
public static final String REGISTRATION_CALLBACK_INTENT = "com.google.android.c2dm.intent.REGISTRATION";
private static final String C2DM_INTENT = "com.google.android.c2dm.intent.RECEIVE";
// Logging tag
private static final String TAG = "C2DM";
// Extras in the registration callback intents.
public static final String EXTRA_UNREGISTERED = "unregistered";
public static final String EXTRA_ERROR = "error";
public static final String EXTRA_REGISTRATION_ID = "registration_id";
public static final String ERR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
public static final String ERR_ACCOUNT_MISSING = "ACCOUNT_MISSING";
public static final String ERR_AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED";
public static final String ERR_TOO_MANY_REGISTRATIONS = "TOO_MANY_REGISTRATIONS";
public static final String ERR_INVALID_PARAMETERS = "INVALID_PARAMETERS";
public static final String ERR_INVALID_SENDER = "INVALID_SENDER";
public static final String ERR_PHONE_REGISTRATION_ERROR = "PHONE_REGISTRATION_ERROR";
// wakelock
private static final String WAKELOCK_KEY = "C2DM_LIB";
private static PowerManager.WakeLock mWakeLock;
private final String senderId;
/**
* The C2DMReceiver class must create a no-arg constructor and pass the
* sender id to be used for registration.
*/
public C2DMBaseReceiver(String senderId) {
// senderId is used as base name for threads, etc.
super(senderId);
this.senderId = senderId;
}
/**
* Called when a cloud message has been received.
*/
protected abstract void onMessage(Context context, Intent intent);
/**
* Called on registration error. Override to provide better
* error messages.
*
* This is called in the context of a Service - no dialog or UI.
*/
public abstract void onError(Context context, String errorId);
/**
* Called when a registration token has been received.
*/
public void onRegistered(Context context, String registrationId) throws IOException {
// registrationId will also be saved
}
/**
* Called when the device has been unregistered.
*/
public void onUnregistered(Context context) {
}
@Override
public final void onHandleIntent(Intent intent) {
Log.d(TAG, "@@@@ - onHandleIntent Messaging request received");
try {
Context context = getApplicationContext();
if (intent.getAction().equals(REGISTRATION_CALLBACK_INTENT)) {
handleRegistration(context, intent);
} else if (intent.getAction().equals(C2DM_INTENT)) {
onMessage(context, intent);
} else if (intent.getAction().equals(C2DM_RETRY)) {
C2DMessaging.register(context, senderId);
}
} finally {
// Release the power lock, so phone can get back to sleep.
// The lock is reference counted by default, so multiple
// messages are ok.
// If the onMessage() needs to spawn a thread or do something else,
// it should use it's own lock.
mWakeLock.release();
}
}
/**
* Called from the broadcast receiver.
* Will process the received intent, call handleMessage(), registered(), etc.
* in background threads, with a wake lock, while keeping the service
* alive.
*/
static void runIntentInService(Context context, Intent intent) {
if (mWakeLock == null) {
// This is called from BroadcastReceiver, there is no init.
PowerManager pm =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
WAKELOCK_KEY);
}
mWakeLock.acquire();
// Use a naming convention, similar with how permissions and intents are
// used. Alternatives are introspection or an ugly use of statics.
String receiver = context.getPackageName() + ".C2DMReceiver";
intent.setClassName(context, receiver);
context.startService(intent);
}
private void handleRegistration(final Context context, Intent intent) {
final String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID);
Log.d(TAG, "@@@@ - HandleRegistration Messaging request received");
String error = intent.getStringExtra(EXTRA_ERROR);
String removed = intent.getStringExtra(EXTRA_UNREGISTERED);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "dmControl: registrationId = " + registrationId +
", error = " + error + ", removed = " + removed);
}
if (removed != null) {
// Remember we are unregistered
C2DMessaging.clearRegistrationId(context);
onUnregistered(context);
return;
} else if (error != null) {
// we are not registered, can try again
C2DMessaging.clearRegistrationId(context);
// Registration failed
Log.e(TAG, "Registration error " + error);
onError(context, error);
if ("SERVICE_NOT_AVAILABLE".equals(error)) {
long backoffTimeMs = C2DMessaging.getBackoff(context);
Log.d(TAG, "Scheduling registration retry, backoff = " + backoffTimeMs);
Intent retryIntent = new Intent(C2DM_RETRY);
PendingIntent retryPIntent = PendingIntent.getBroadcast(context,
0 /*requestCode*/, retryIntent, 0 /*flags*/);
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.ELAPSED_REALTIME,
backoffTimeMs, retryPIntent);
// Next retry should wait longer.
backoffTimeMs *= 2;
C2DMessaging.setBackoff(context, backoffTimeMs);
}
} else {
try {
onRegistered(context, registrationId);
C2DMessaging.setRegistrationId(context, registrationId);
} catch (IOException ex) {
Log.e(TAG, "Registration error " + ex.getMessage());
}
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
你说你“拥有我的 Android 应用程序上注册用户的身份验证令牌”。您可能刚刚写错了,但如果您的字面意思是您正在使用注册用户的身份验证令牌,而不是用户从 C2DM 服务器返回的注册 ID ,那么你的问题就在那里。
编辑:您的客户端应用程序(在设备上运行)使用 C2DM 的 3 步流程:1) 调用 C2DM 服务器传递客户端的 Gmail 帐户 ID 和密码,获取身份验证令牌; 2) 使用步骤 1 中的身份验证令牌再次调用 C2DM 服务器,获取注册 ID(这是 ASCII splooge 的 96-120 个字符); 3) 调用您的服务器应用程序并传递在步骤 2 中获取的注册 ID(而不是在步骤 1 中获取的身份验证令牌)。
当您的服务器应用程序想要将某些内容推送到客户端时,它会调用 C2DM 服务器以获取身份验证令牌(传递您用于注册 C2DM 服务器的电子邮件和密码,而不客户端用户的电子邮件和密码),然后使用该身份验证令牌以及客户端的注册 ID 来执行推送。
编辑2:我在这里对客户端上发生的情况的描述是错误的 - 客户端代码在任何时候都不涉及获取oauth令牌。所有这些事情都是由 Android 操作系统本身处理的。本教程:
http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html< /a>
很好地展示了 C2DM 的一切工作原理。
编辑 3: 我在 C2DM 中看到的最常见错误来自于文档中使用短语“发件人的电子邮件”。该术语是指“注册”用于C2DM 的gmail 帐户,而不是指Android 用户的gmail 帐户。您的 Web 服务器应用程序使用此 Gmail 帐户(以及匹配的密码)从 C2DM 获取 oauth 令牌。 Android 客户端应用程序需要使用同一个帐户(没有匹配的密码,它不知道该密码)来进行调用,以返回注册 ID,然后将其发送到您的 Web 服务器。
You say you "have the auth token from the registered user on my android app". You may have just written that incorrectly, but if you mean literally that you're using the registered user's auth token and not the registration id the user got back from the C2DM server, then there's your problem right there.
Edit: Your client app (running on the device) uses a 3-step process for C2DM: 1) call the C2DM server passing the client's gmail account id and password, get back an auth token; 2) call the C2DM server again using the auth token from step 1, get back a registration id (which is 96-120 characters of ASCII splooge); 3) call your server app and pass the registration id obtained in step 2 (not the auth token obtained in step 1).
When your server app then wants to push something to the client, it makes a call to the C2DM server to get an auth token (passing the email and password you used to sign up for the C2DM server, not the client user's email and password), then uses that auth token along with the client's registration id to execute the push.
Edit 2: my description here of what happens on the client is wrong - the client code does not involve getting an oauth token at any point. All of that stuff is handled by the Android OS itself. This tutorial:
http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html
shows nicely how everything works for C2DM.
Edit 3: The most common mistake I've seen with C2DM comes from the documentation's use of the phrase "sender's email". This term refers to the gmail account that was "registered" for use with C2DM, and does not refer to the gmail account of the Android user. This gmail account is used by your web server app (along with the matching password) to get an oauth token from C2DM. This same account needs to be used by the Android client app (without the matching password, which it doesn't know) to make the call that gets back a registrationId that it then sends to your web server.