【问题标题】:Google Cloud Messaging (GCM) with local device groups on Android gives HTTP Error code 401带有 Android 本地设备组的 Google Cloud Messaging (GCM) 提供 HTTP 错误代码 401
【发布时间】:2016-03-10 00:02:30
【问题描述】:

我正在尝试让 Google Cloud Messaging (GCM) 在 Android 上使用https://developers.google.com/cloud-messaging/android/client-device-group 中所述的本地设备组工作。

我在 Web 控制台 (https://console.developers.google.com) 中启用了以下服务:

  • 适用于 Android 的 Google 云消息传递
  • 谷歌云发布/订阅
  • Google+ API

我还创建了以下凭据:

  • API 密钥(带有指定的包名称和 SHA-1 证书密钥)
  • OAuth 2.0 客户端 ID(Web 应用程序)

我的 Android 代码如下所示(错误处理等已被剥离):

String account = ACCOUNT_NAME; // E.g. "johndoe@google.com"
String scope = "audience:server:client_id:" + WEB_APPLICATION_CLIENT_ID;
String token = GoogleAuthUtil.getToken(context, account, scope);

// Token is successfully found here :-)

URL url = new URL("https://android.googleapis.com/gcm/notification");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestProperty("project_id", NUMERICAL_PROJECT_ID);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.connect();

JSONObject requestBody = new JSONObject();
requestBody.put("operation", "add");
requestBody.put("notification_key_name", "foobar");
requestBody.put("registration_ids", 
  new JSONArray(Arrays.asList(new String[]{"42", "44"})));
requestBody.put("id_token", token);

OutputStream os = connection.getOutputStream();
os.write(requestBody.toString().getBytes("UTF-8"));
os.close();

// connection.getResponseCode() is now 401

提交给https://android.googleapis.com/gcm/notification 的 JSON 如下所示:

{
  "operation": "add",
  "notification_key_name": "foobar",
  "registration_ids": ["42","44"],
  "id_token": "[veeeeeery long string]"
}

响应内容为:

<HTML>
<HEAD>
<TITLE>Unauthorized</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000">
<H1>Unauthorized</H1>
<H2>Error 401</H2>
</BODY>
</HTML>

我做错了什么?

【问题讨论】:

  • 为了确定,已经运行了来自developers.google.com/cloud-messaging/android/start的第4部分示例
  • 很明显,您已经付出了彻底的努力来完成这项工作。我也尝试过,但失败了。对于同一问题还有另外两个 SO 问题,one recentthe other 来自一年前。两者都没有解决办法。 instructions 显然是错误的或不完整的。非常沮丧!
  • 是的。我从示例开始并尝试将其移动到本地设备组,因为我不希望在解决方案架构中有不必要的应用服务器。这些示例假设您有一个可用的应用服务器。

标签: android oauth-2.0 google-cloud-messaging


【解决方案1】:

找到诀窍:您正在使用 google 帐户获取 id_token,您需要完全使用电子邮件作为 notification_key_name。所以如果你使用 foo@gmail.com,你需要使用这个地址作为 notification_key_name。

【讨论】:

  • 在多个答案中发送相同的答案并不合适。如果您可以复制并粘贴答案,则问题可能是重复的。投票关闭它们。
  • 我试过了,但我不能,我不知道为什么我找不到标记为重复的响应。对不起。由于有很多人在寻找回复,因此最好在所有相关问题上发送垃圾邮件。如果版主想删除它们对我来说是可以的。我只是试图帮助其他开发人员。编辑:您似乎无法将其标记为重复,因为原始问题必须有一个可接受的答案。
  • 你是绝对正确的。这成功了。非常感谢! -- 只是一个奇怪的问题:你是怎么想出来的?
  • 旁注:我已通知 Google 他们应该使用此信息更新文档。 :-)
【解决方案2】:

以下是根据greywolf82 的正确答案的摘要。正确的代码应该遵循这些原则(错误处理等已被剥离):

///////////////////////////////////////////////////////////////////////////
// Working example on how to create a locally (client-side) managed      //
// device group for Google Cloud Messaging.                              //
//                                                                       //
// Thanks to greywolf82 for adding the final piece.                      //
// Please vote on his answer. Thank you!                                 //
///////////////////////////////////////////////////////////////////////////

// Get token:
String account = ACCOUNT_NAME; // E.g. "johndoe@gmail.com"
String scope = "audience:server:client_id:" + WEB_APPLICATION_CLIENT_ID;
String idToken = GoogleAuthUtil.getToken(context, account, scope);

// Get registration id:
InstanceID instanceID = InstanceID.getInstance(this);
String registration_id = instanceID.getToken(
        getString(R.string.gcm_defaultSenderId),
        GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);

// Set up HTTP connection:
URL url = new URL("https://android.googleapis.com/gcm/googlenotification");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestProperty("project_id", NUMERICAL_PROJECT_ID);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.connect();

JSONObject requestBody = new JSONObject();
requestBody.put("operation", "add");
requestBody.put("notification_key_name", ACCOUNT_NAME); // You *must* use the email!
requestBody.put("registration_ids",
    new JSONArray(Arrays.asList(new String[]{registration_id})));
requestBody.put("id_token", idToken);

// Submit request body
OutputStream os = connection.getOutputStream();
os.write(requestBody.toString().getBytes("UTF-8"));
os.close();

// connection.getResponseCode() is now 200  :-)
// Now read the server response contents from connection.getInputStream()

提交给https://android.googleapis.com/gcm/notification 的 JSON 如下所示:

{
  "operation": "add",
  "notification_key_name": "johndoe@gmail.com",
  "registration_ids": ["very long string here"],
  "id_token": "another very long string"
}

响应内容为:

{
  "notification_key": "your notification key is here --- voilá!"
}

【讨论】:

    【解决方案3】:

    这是使用 SERVER_API_KEY 完成的方式(不是最佳实践,但我已经达到了最好的):

    首先,获取一个应用实例id:

    InstanceID instanceID = InstanceID.getInstance(this);
    String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
        GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
    
    // Variable "token" now has the instance id. :-)
    

    接下来,获取消息 id 令牌:

    String account = ACCOUNT_NAME; // E.g. "johndoe@google.com"
    String scope = "audience:server:client_id:" + WEB_APPLICATION_CLIENT_ID;
    String idToken = GoogleAuthUtil.getToken(context, account, scope);
    
    // Variable "idToken" now hods the messaging id token. :-)
    

    现在到神奇的部分。创建一个以实例 ID 作为成员的新设备组:

    URL url = new URL("https://gcm-http.googleapis.com/gcm/notification"); // <-- Use this URL!
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("POST");
    connection.setDoOutput(true);
    connection.setDoInput(true);
    connection.setRequestProperty("Authorization", "key=" + SERVER_API_KEY); // <--- Auth!
    connection.setRequestProperty("project_id", NUMERICAL_PROJECT_ID);
    connection.setRequestProperty("Content-Type", "application/json");
    connection.setRequestProperty("Accept", "application/json");
    connection.connect();
    
    JSONObject requestBody = new JSONObject();
    requestBody.put("operation", "create");               // <--- Not "add"
    requestBody.put("notification_key_name", "foobar");
    requestBody.put("registration_ids", 
        new JSONArray(Arrays.asList(new String[]{token}))); // <--- Instance Id token here!
    requestBody.put("id_token", idToken);
    
    OutputStream os = connection.getOutputStream();
    os.write(requestBody.toString().getBytes("UTF-8"));
    os.close();
    
    InputStream is = connection.getInputStream();
    String responseString = new Scanner(is, "UTF-8").useDelimiter("\\A").next();
    is.close();
    
    JSONObject response = new JSONObject(responseString);
    Log.d(TAG, "Server response:\n" + response.toString(4));
    String notificationKey = response.getString("notification_key");
    

    此时,变量“notificationKey”保存了通知密钥,这是向该设备组发送消息时必须使用的接收方。

    发送这样的消息(使用“notificationKey”值作为参数作为上面的接收者):

    private void sendMessage(String recipient) throws IOException, JSONException {
    
        Log.i(TAG, "Sending message to: " + recipient);
        URL url = new URL("https://gcm-http.googleapis.com/gcm/send");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);
        connection.setDoInput(true);
    
        Log.d(TAG, "Opening connection to " + url.toString());
    
        connection.setRequestProperty("Authorization", "key=" + SERVER_API_KEY);
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("Accept", "application/json");
        connection.connect();
    
        JSONObject requestBody = new JSONObject();
        requestBody.put("to", recipient);
        JSONObject requestData = new JSONObject();
        requestData.put("hello", "Hello World!");
        requestData.put("hellodata", "42");
        requestBody.put("data", requestData);
    
        Log.d(TAG, "Request body:\n" + requestBody.toString(4));
    
        OutputStream os = connection.getOutputStream();
        os.write(requestBody.toString().getBytes("UTF-8"));
        os.close();
    
        Log.d(TAG, "Response code: " + connection.getResponseCode());
    
        InputStream is = connection.getInputStream();
        String responseString = new Scanner(is, "UTF-8").useDelimiter("\\A").next();
        is.close();
    }
    

    在不使用服务器 API 密钥的情况下,我无法成功执行此操作。在 https://http.googleapis.com/gcm/googlenotification 端点上使用不同的 Authorization 标头值进行调查可能是值得的。

    希望这是有道理的。玩得开心。 :-)

    【讨论】:

    • 好吧——干得好,但这并不能真正回答你最初提出的问题。您问题中链接的说明用于设备组的客户端管理。您的解决方案使用旨在在安全服务器上运行的处理来创建组。因为该处理使用 API 密钥,所以将其放在客户端设备中并不安全。黑客可以反编译代码以获取密钥。 (续)
    • 您的问题中链接的说明指出:“管理客户端上的设备组对于服务器不可用的情况很有用。...请注意,在客户端上创建通知密钥的过程非常重要不同于Device Group Messaging"中描述的服务器端进程。我对文档的理解是,客户端应该可以仅使用 OAuth Web 应用程序客户端 ID 创建组并发送消息——无需 API 密钥。
    • 最后你正在模拟服务器。它可以工作,但它与客户端解决方案不同。我尝试使用不同的授权密钥(即使不需要阅读文档),我尝试更改令牌,我尝试使用不同的帐户,什么都没有,什么都没有。它似乎完全坏了。
    • 同意。好像坏了- 我急切地等待 GCM 开发人员支持的电子邮件回复,他们已经多次回复了我的问题(指的是这个确切的线程)。现在,我好心地要求一个工作示例,但我还没有听到任何消息。我们只能希望.... :-/
    • @MortenE.Rasmussen 有我可以用来发送帮助请求的电子邮件吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多