Token-based APNs C#

token-based authentication APNS推播範例使用C#

廖汶彬 2016/11/22 00:45:48
7705







主題

token-based authentication APNS推播範例使用C#

文章簡介

AppleWWDC 2016推出一個關於推播服務的新功能token進行身份驗證並且通過HTTP/2發送推播,優點是不再需要一個APP一個憑証,也不需要擔心憑証效期的問題本篇文章主要說明如何申請加密私鑰並且使用C#token-based authentication方式發送推播

作者

廖汶彬

版本/產出日期

V1.0/2016/11/21




1. 前言

Apple於WWDC 2016推出一個關於推播服務的新功能,以token進行身份驗證並且通過HTTP/2發送推播。原來的推播方式需要透過推播憑証來進行驗證,正常來說憑証都會有有效期,當憑証過期之後我們需要去做一個更新的動作而且通常一個APP就需要一個憑証還分為開發與正版本,但如果使用token進行身份驗証進行推播就再也不需要擔心推播憑証過期的問題也不會被一大堆推播憑証搞的頭昏腦脹。


Apple官方說明文件: Use Authentication Tokens Communicating with APNs


接下來直接說明如何申請APNs Auth Key並在C#的主控台(Console)應用程式產生JWT再透過HTTP/2發送推播通知。

2. 開發工具

Visual Studio 2015(.Net framework 4.6.2)

由於加密演算法的需要

電腦必需安裝.Net framework 4.6.2以上版本


3. 使用套件

HttpTwo(0.0.1.19)

jose-jwt(2.0.2)

Josn.Net(9.0.1)



4. 如何申請APNs Auth Key

登入Apple Developer進入Certificates, Identifiers & Profiles

新增iOS Certificates


選取Production分類的Apple Push Notification Authentication Key (Sandbox & Production)按下確定建立APNs Auth Key


下載APNs Auth Key檔案(.p8)



5. 利用APNs Auth Key產生Json web token

產生Json web token資料格式

Header

{

"alg": "ES256",

"kid": "ABC123DEFG"

}

Claim

{

"iss": "DEF123GHIJ",

"iat": 1437179036

}

說明:

alg (Algorithm): 使用的加密演算法(ES256).

kid (Key ID): 前一步驟產生之APNs Auth KeyKey ID.

iss (Issuer): 開發者帳號的Team ID.

iat (Issued At): 產生Token的時間(UTC)並且以秒為單位.


利用jose-jwt (2.0.2) 套件產生Json web token詳細程式碼

//讀取.p8檔案取得加密私鑰

var privateKeyContent = System.IO.File.ReadAllText(authKeyPath);

var privateKey = privateKeyContent.Split('\n')[1];


//將私鑰轉換為CngKey物件,供後續產生JWT使用

var secretKeyFile = Convert.FromBase64String(privateKey);

var secretKey = CngKey.Import(secretKeyFile, CngKeyBlobFormat.Pkcs8PrivateBlob);


//取得產生Token的時間(UTC)秒為單位

var expiration = DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

var expirationSeconds = (long)expiration.TotalSeconds;


//產生JWT的資料物件

var payload = new Dictionary<string, object>()

{

{ "iss", teamId },

{ "iat", expirationSeconds }

};

var header = new Dictionary<string, object>()

{

{ "alg", algorithm },

{ "kid", apnsKeyId }

};


//利用jose-jwt套件產生Json web token

string accessToken = Jose.JWT.Encode(payload, secretKey, JwsAlgorithm.ES256, header);



6. 使用HttpClientHTTP/2發送推播

請注意推播服務一樣有區分為開發與正式環境

Development server : api.development.push.apple.com:443

Production server : api.push.apple.com:443


建立HttpClient利用 HttpTwo(0.0.1.19) 套件透過Http/2發送要求

設定request headers與推播payload資料

請參考 官方說明文件 中的Table 8-2 APNs request headers


詳細程式碼

//Development server:api.development.push.apple.com:443

//Production server:api.push.apple.com:443

string host = "api.development.push.apple.com";

int port = 443;


// Uri to request

var uri = new Uri(string.Format("https://{0}:{1}/3/device/{2}", host, port, registrationId));


//建立推播資料

var payloadData = JObject.FromObject(new

{

aps = new

{

alert = "Notification Message.",

badge = 2,

sound = "ping.aiff"

}

});

//UTF8編碼避免中文無法正常顯示

byte[] data = System.Text.Encoding.UTF8.GetBytes(payloadData.ToString());


//建立HttpClient物件使用HTTP/2

var handler = new Http2MessageHandler();

var httpClient = new HttpClient(handler);


//建立HttpRequestMessage,依據官方要求填入對應的資訊

//包含tokenAPPbundle Id等訊息

var requestMessage = new HttpRequestMessage();

requestMessage.RequestUri = uri;

requestMessage.Headers.Add("authorization", string.Format("bearer {0}", accessToken));

requestMessage.Headers.Add("apns-id", Guid.NewGuid().ToString());

requestMessage.Headers.Add("apns-expiration", "0");

requestMessage.Headers.Add("apns-priority", "10");

requestMessage.Headers.Add("apns-topic", bundleId);

requestMessage.Method = HttpMethod.Post;

requestMessage.Content = new ByteArrayContent(data);


try

{

//發送推播

var responseMessage = await httpClient.SendAsync(requestMessage);

//取得發送結果顯示對應訊息

if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)

{

string responseUuid = string.Empty;

IEnumerable<string> values;

if (responseMessage.Headers.TryGetValues("apns-id", out values))

{

responseUuid = values.First();

}

Console.WriteLine(string.Format("\n\r*******Send Success [{0}]", responseUuid));

}

else

{

var body = await responseMessage.Content.ReadAsStringAsync();

var json = new JObject();

json = JObject.Parse(body);


var reasonStr = json.Value<string>("reason");

Console.WriteLine("\n\r*******Failure reason => " + reasonStr);

}

}

catch (Exception ex)

{

Console.WriteLine("\n\r*******Exception message => " + ex.Message);

}



7. 程式碼(GitHub)

https://github.com/MonkeyBinBin/TokenbasedAPNsSample

專案不含私鑰檔案與詳細設定

請參考程式中的說明與官方文件


廖汶彬