How to implement apple token based push notificati

2019-03-27 01:17发布

问题:

For an app with some kind of chat based features I want to add push notification support for receiving new messages. What I want to do is use the new token based authentication (.p8 file) from Apple, but I can't find much info about the server part.

I came across the following post: How to use APNs Auth Key (.p8 file) in C#?

However the answer was not satisfying as there was not much detail about how to:

  • establish a connection with APNs
  • use the p8 file (except for some kind of encoding)
  • send data to the Apple Push Notification Service

回答1:

You can't really do this on raw .NET Framework at the moment. The new JWT-based APNS server uses HTTP/2 only, which .NET Framework does not yet support.

.NET Core's version of System.Net.Http, however, does, provided you meet the following prerequisites:

  • On Windows, you must be running Windows 10 Anniversary Edition (v1607) or higher, or the equivalent build of Windows Server 2016 (I think).
  • On Linux, you must have a version of libcurl that supports HTTP/2.
  • On macOS, you have to compile libcurl with support for HTTP/2, then use the DYLD_INSERT_LIBRARIES environment variable in order to load your custom build of libcurl.

You should be able to use .NET Core's version of System.Net.Http in the .NET Framework if you really want.

I have no idea what happens on Mono, Xamarin or UWP.

There are then three things you have to do:

  1. Parse the private key that you have been given. This is currently an ECDSA key, and you can load this into a System.Security.Cryptography.ECDsa object.
    • On Windows, you can use the CNG APIs. After parsing the base64-encoded DER part of the key file, you can then create a key with new ECDsaCng(CngKey.Import(data, CngKeyBlobFormat.Pkcs8PrivateBlob)).
    • On macOS or Linux there is no supported API and you have to parse the DER structure yourself, or use a third-party library.
  2. Create a JSON Web Token / Bearer Token. If you use the System.IdentityModel.Tokens.Jwt package from NuGet, this is fairly simple. You will need the Key ID and Team ID from Apple.
public static string CreateToken(ECDsa key, string keyID, string teamID)
{
    var securityKey = new ECDsaSecurityKey(key) { KeyId = keyID };
    var credentials = new SigningCredentials(securityKey, "ES256");

    var descriptor = new SecurityTokenDescriptor
    {
          IssuedAt = DateTime.Now,
          Issuer = teamID,
          SigningCredentials = credentials
    };

    var handler = new JwtSecurityTokenHandler();
    var encodedToken = handler.CreateEncodedJwt(descriptor);
    return encodedToken;
}
  1. Send an HTTP/2 request. This is as normal, but you need to do two extra things:

    1. Set yourRequestMessage.Version to new Version(2, 0) in order to make the request using HTTP/2.
    2. Set yourRequestMessage.Headers.Authorization to new AuthenticationHeaderValue("bearer", token) in order to provide the bearer authentication token / JWT with your request.

    Then just put your JSON into the HTTP request and POST it to the correct URL.



回答2:

You can use PushSharp which is a nuget package which support push notification for Apple and also for Google and Microsoft.

Here are the links to github and nuget.

This is a sample to send push notification for Apple:

// Configuration (NOTE: .pfx can also be used here)
var config = new ApnsConfiguration (ApnsConfiguration.ApnsServerEnvironment.Sandbox, 
"push-cert.p12", "push-cert-pwd");

// Create a new broker
var apnsBroker = new ApnsServiceBroker (config);

// Wire up events
apnsBroker.OnNotificationFailed += (notification, aggregateEx) => {

aggregateEx.Handle (ex => {

    // See what kind of exception it was to further diagnose
    if (ex is ApnsNotificationException) {
        var notificationException = (ApnsNotificationException)ex;

        // Deal with the failed notification
        var apnsNotification = notificationException.Notification;
        var statusCode = notificationException.ErrorStatusCode;

        Console.WriteLine ($"Apple Notification Failed: ID={apnsNotification.Identifier}, Code={statusCode}");

    } else {
        // Inner exception might hold more useful information like an ApnsConnectionException           
        Console.WriteLine ($"Apple Notification Failed for some unknown reason : {ex.InnerException}");
    }

        // Mark it as handled
        return true;
    });
};

    apnsBroker.OnNotificationSucceeded += (notification) => {
    Console.WriteLine ("Apple Notification Sent!");
};

// Start the broker
apnsBroker.Start ();

foreach (var deviceToken in MY_DEVICE_TOKENS) {
// Queue a notification to send
apnsBroker.QueueNotification (new ApnsNotification {
    DeviceToken = deviceToken,
    Payload = JObject.Parse ("{\"aps\":{\"badge\":7}}")
    });
}

// Stop the broker, wait for it to finish   
// This isn't done after every message, but after you're
// done with the broker
apnsBroker.Stop ();


回答3:

private string GetToken()
    {
        var dsa = GetECDsa();
        return CreateJwt(dsa, "keyId", "teamId");
    }

    private ECDsa GetECDsa()
    {
        using (TextReader reader = System.IO.File.OpenText("AuthKey_xxxxxxx.p8"))
        {
        var ecPrivateKeyParameters =
            (ECPrivateKeyParameters)new Org.BouncyCastle.OpenSsl.PemReader(reader).ReadObject();

        var q = ecPrivateKeyParameters.Parameters.G.Multiply(ecPrivateKeyParameters.D).Normalize();
        var qx = q.AffineXCoord.GetEncoded();
        var qy = q.AffineYCoord.GetEncoded();
        var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned();

        // Convert the BouncyCastle key to a Native Key.
        var msEcp = new ECParameters {Curve = ECCurve.NamedCurves.nistP256, Q = {X = qx, Y = qy}, D = d};
        return ECDsa.Create(msEcp);
        }
    }

    private string CreateJwt(ECDsa key, string keyId, string teamId)
    {
        var securityKey = new ECDsaSecurityKey(key) { KeyId = keyId };
        var credentials = new SigningCredentials(securityKey, "ES256");

        var descriptor = new SecurityTokenDescriptor
        {
            IssuedAt = DateTime.Now,
            Issuer = teamId,
            SigningCredentials = credentials,

        };

        var handler = new JwtSecurityTokenHandler();
        var encodedToken = handler.CreateEncodedJwt(descriptor);
        return encodedToken;
    }