Creating registration ID for Azure Notification Hu

2019-09-16 21:07发布

问题:

I'm building an app in cordova, and i want to use an Azure Notification Hub to send push notifications to the app. I've had some issues with the Notification Hub Cordova Plugin, as it fails to build for 64-bit architecture - hence it's this plugin is not an option for me. Instead i'm using the Notification Hub REST api. However i'm stuck trying to create a registration id from the device. I'm able to send an AJAX-request to my Notification Hub, but i keep getting a 401 error.

Here's my js code so far:

    function registerHub() {
  var connectionString = 'Endpoint=sb://[service-bus-name].servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=[my-shared-access-key]';

  var parts = connectionString.split(';');
  if(parts.length != 3)
  throw "Error parsing connection string";

  parts.forEach(function(part){
    if (part.indexOf('Endpoint') == 0){
      endpoint = 'https' + part.substring(11);
      console.log('endpoint ' + endpoint);
    } else if(part.indexOf('SharedAccessKeyName') == 0){
      sasKeyName = part.substring(20);
      console.log('sasKeyName ' + sasKeyName);
    } else if(part.indexOf('SharedAccessKey') == 0) {
      sasKeyValue = part.substring(16);
      console.log('sasKeyValue ' + sasKeyValue);
    }
  });

  var getSelfSignedToken = function(resourceUri, sharedKey, ruleId, expiresInMins) {
    targetUri = encodeURIComponent(resourceUri.toLowerCase()).toLowerCase();

    var expireOnDate = new Date();
    expireOnDate.setMinutes(expireOnDate.getMinutes() + expiresInMins);
    var expires = Date.UTC(expireOnDate.getUTCFullYear(), expireOnDate.getUTCMonth(), expireOnDate.getUTCDate(), expireOnDate.getUTCHours(), expireOnDate.getUTCMinutes(), expireOnDate.getUTCSeconds()) / 1000;
    console.log('expires in epoch: ' + expires);
    var tosign = resourceUri + "\n" + expires;

    var signature = CryptoJS.HmacSHA256(tosign, sharedKey);
    var base64signature = signature.toString(CryptoJS.enc.base64);
    var base64UriEncoded = encodeURIComponent(base64signature);

    console.log('signature: ' + base64UriEncoded + ' tosign ' + tosign);

    var token = "SharedAccessSignature sr=" + targetUri + "&sig=" + base64UriEncoded + "&se=" + expires + "&skn=" + ruleId;

    return token;
  };

  var targetUri = 'http://[service-bus-namespace].servicebus.windows.net/[notification-hub-name]';
  var hubPath = [hub path];
  var sharedKey = sasKeyValue;
  var ruleId = sasKeyName;
  var expiresInMins = 129600;



    var createRegistrationId = function(targetUri, endpoint) {
      console.log('create registration endpoint: ' + endpoint + ' targetUri ' + targetUri);
      var registrationPath = hubPath + "/registrations/";
      var serverUrl = endpoint + registrationPath + "?api-version=2015-01";
      console.log('serverUrl ' + serverUrl);

      var token = getSelfSignedToken(targetUri, sharedKey, ruleId, expiresInMins);
      var deferred = $.Deferred();
      $.ajax({
        type: "POST",
        url: serverUrl,
        headers: {
          "Content-Type": "application/atom+xml;type=entry;charset=utf-8",
          "Authorization" : token,
          "x-ms-version": "2015-01"
        },
        beforeSend: function(data){
          console.log('before sending. Current token: ' + token);
        }
      }).done(function(data, status, response) {
        console.log('done response' + JSON.stringify(response) + ' status ' + JSON.stringify(status) + ' data ' + JSON.stringify(data));
        var location = response.getResponseHeader("Content-Location");
        deferred.resolve(location);
      }).fail(function(response, status, error) {
        console.log("Error: " + error + ' error status ' + JSON.stringify(status) + ' error response ' + JSON.stringify(response));
        deferred.reject("Error: " + error);
      });

      return deferred.promise();
    };

    var registrationId = createRegistrationId(targetUri, endpoint);
    console.log('regId: ' + JSON.stringify(registrationId));

}

The posted javascript successfully generates the variables: "endpoint", "sasKeyName" and "sasKeyValue". From these variables my getSelfSignedToken-function generates and returns a token. So far so good. However my ajax-call to the Notification Hub keeps returning a 401 error code: Error: 40103: Invalid authorization token The complete JSON-response is:

{"readyState":4,"responseText":"","status":401,"statusText":"40103: Invalid authorization token signature"}

I've followed the code snippets from the Azure Notification Hub REST api. Common Concepts: https://msdn.microsoft.com/en-us/library/azure/dn495627.aspx Use REST api from device: https://msdn.microsoft.com/en-us/library/azure/dn495631.aspx

I've tried using the SAS root key for my entire servicebus, i've also tried calling /registrationIDs/ instead of /registrations/ (as stated in the documentation about creating registration id - confusing as the code snippets in the link above, refers to /registration/, which i'm using in the code above). I've tried with only "Authorization": token in the header of my ajax-request, and with the x-ms-version and content-type (as in the code above). But always with the same response.

I can't grasp if i'm using the right serverUrl by reading the Azure Documentation. Also i'm unsure if my token is generated correctly, although it looks right when i console.log it before send. I'm sure that i'm missing some small detail, but i've been twisting my head around this for a couple of days now. So if anybody successfully created registration ID via the REST api, i would really like a break down of the token generation and how to construct the serverUrl.

Best regards

EDIT: Forgot to add, that i'm testing on iPhone 6, iOS 8.4 (real device not emulator).

EDIT#2: My token looks like this at the moment:

SharedAccessSignature sr=https%3a%2f%2f[service-bus-namespace].servicebus.windows.net%2f[notification-hub-name]&sig=7d164dcbeadd3c79248e94ece032cfb689c9890de4dc1e4959a961889860bb18&se=1444208043&skn=DefaultFullSharedAccessSignature

Does this look right?

回答1:

var hubName = '<Enter Your Hub Name>';
var connectionString = '<Enter your DefaultFullSharedAccessSignatire>';
var apiVersion = "?api-version=2015-01";
var expiresInMins = 129600;

var endpoint;
var sasKeyValue;
var sasKeyName;
var targetUri;

function ParseHubConnectionString() {

    var parts = connectionString.split(';');
    if(parts.length != 3)
        throw "Error parsing connection string";

    parts.forEach(function(part){
        if (part.indexOf('Endpoint') == 0){
            endpoint = 'https' + part.substring(11);
        } else if(part.indexOf('SharedAccessKeyName') == 0){
            sasKeyName = part.substring(20);
        } else if(part.indexOf('SharedAccessKey') == 0) {
            sasKeyValue = part.substring(16);
        }
    });

    targetUri = endpoint + hubName;
}


var getSelfSignedToken = function (resourceUri, sharedKey, ruleId, expiresInMins) {
    resourceUri = encodeURIComponent(resourceUri.toLowerCase()).toLowerCase();

    var expireOnDate = new Date();
    expireOnDate.setMinutes(expireOnDate.getMinutes() + expiresInMins);
    var expires = Date.UTC(expireOnDate.getUTCFullYear(), expireOnDate.getUTCMonth(), expireOnDate.getUTCDate(), expireOnDate.getUTCHours(), expireOnDate.getUTCMinutes(), expireOnDate.getUTCSeconds()) / 1000;
    var tosign = resourceUri + "\n" + expires;

    // using CryptoJS
    var signature = CryptoJS.HmacSHA256(tosign, sharedKey);
    var base64signature = signature.toString(CryptoJS.enc.Base64);
    var base64UriEncoded = encodeURIComponent(base64signature);

    var token = "SharedAccessSignature sr=" + resourceUri + "&sig=" + base64UriEncoded + "&se=" + expires + "&skn=" + ruleId;

    return token;
};




var createRegistrationId = function(Uri, endpoint) {

    var registrationPath = Uri + "/Registrations/";
    var resourceUri = registrationPath + apiVersion;

    var token = getSelfSignedToken(resourceUri, sasKeyValue, sasKeyName, expiresInMins);
    var deferred = $.Deferred();
    $.ajax({
        type: "POST",
        url: resourceUri,
        data: getChannelData(),
        headers: {
            "Content-Type": "application/atom+xml;type=entry;charset=utf-8",
            "Authorization": token,
            "x-ms-version": "2015-01"
        },
        beforeSend: function(data){
            console.log('before sending. Current token: ' + token);
        }
    }).done(function(data, status, response) {
        console.log('done response' + JSON.stringify(response) + ' status ' + JSON.stringify(status) + ' data ' + JSON.stringify(data));
        var location = response.getResponseHeader("Content-Location");
        deferred.resolve(location);
    }).fail(function(response, status, error) {
        console.log("Error: " + error + ' error status ' + JSON.stringify(status) + ' error response ' + JSON.stringify(response));
        deferred.reject("Error: " + error);
    });

    return deferred.promise();
};


回答2:

Can you verify that components/enc-base64.js is included? Otherwise, the following line may generate an incorrect value that can result in a 401...

var base64signature = signature.toString(CryptoJS.enc.Base64);