J. *_*ati 10 c# apple-push-notifications
对于具有某种基于聊天功能的应用,我想添加推送通知支持以接收新消息.我想要做的是使用Apple的基于新令牌的身份验证(.p8文件),但我找不到有关服务器部分的更多信息.
我遇到了以下帖子: 如何在C#中使用APNs Auth Key(.p8文件)?
然而,答案并不令人满意,因为没有太多关于如何:
yaa*_*kov 10
目前,您无法在原始.NET Framework上实现此目的.新的基于JWT的APNS服务器仅使用HTTP/2,.NET Framework尚不支持.
System.Net.Http但是,只要满足以下先决条件,.NET Core的版本就可以了:
libcurl支持HTTP/2 的版本.libcurl支持HTTP/2,然后使用DYLD_INSERT_LIBRARIES环境变量来加载您的自定义构建libcurl.System.Net.Http如果你真的想要,你应该可以在.NET Framework中使用.NET Core的版本.
我不知道在Mono,Xamarin或UWP上会发生什么.
那么你需要做三件事:
System.Security.Cryptography.ECDsa对象中.
new ECDsaCng(CngKey.Import(data, CngKeyBlobFormat.Pkcs8PrivateBlob)).System.IdentityModel.Tokens.JwtNuGet 的软件包,这很简单.您将需要Apple的密钥ID和团队ID.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;
}Run Code Online (Sandbox Code Playgroud)
发送HTTP/2请求.这是正常的,但你需要做两件额外的事情:
yourRequestMessage.Version为new Version(2, 0)使用HTTP/2发出请求.yourRequestMessage.Headers.Authorization为new AuthenticationHeaderValue("bearer", token)为您的请求提供承载认证令牌/ JWT.然后将您的JSON放入HTTP请求并将其发布到正确的URL.
小智 6
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, SecurityAlgorithms.EcdsaSha256);
var descriptor = new SecurityTokenDescriptor
{
IssuedAt = DateTime.Now,
Issuer = teamId,
SigningCredentials = credentials,
};
var handler = new JwtSecurityTokenHandler();
var encodedToken = handler.CreateEncodedJwt(descriptor);
return encodedToken;
}
Run Code Online (Sandbox Code Playgroud)
由于 Token (.p8) APN 仅适用于 HTTP/2,因此大多数解决方案仅适用于 .net Core。由于我的项目使用.net Framework,因此需要进行一些调整。如果您像我一样使用 .net Framework,请继续阅读。
我到处搜索并遇到了几个问题,我设法解决了这些问题并将它们拼凑在一起。
下面是实际有效的 APNs 类。我为其创建了一个新的类库,并将 .P8 文件放置在类库的 AuthKeys 文件夹中。请记住右键单击 .P8 文件并将其设置为“始终复制”。请参阅获取 Web 项目引用的类库项目中的相对文件路径。
之后,要获取 P8 文件的位置,请用于AppDomain.CurrentDomain.RelativeSearchPathWeb 项目或AppDomain.CurrentDomain.BaseDirectorywin 应用程序。请参阅为什么 AppDomain.CurrentDomain.BaseDirectory 在 asp.net 应用程序中不包含“bin”?
要从 P8 获取令牌,您需要使用BouncyCastle类,请从 Nuget 下载它。
using Jose;
using Newtonsoft.Json;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Security.Cryptography;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace PushLibrary
{
public class ApplePushNotificationPush
{
//private const string WEB_ADDRESS = "https://api.sandbox.push.apple.com:443/3/device/{0}";
private const string WEB_ADDRESS = "https://api.push.apple.com:443/3/device/{0}";
private string P8_PATH = AppDomain.CurrentDomain.RelativeSearchPath + @"\AuthKeys\APNs_AuthKey.p8";
public ApplePushNotificationPush()
{
}
public async Task<bool> SendNotification(string deviceToken, string title, string content, int badge = 0, List<Tuple<string, string>> parameters = null)
{
bool success = true;
try
{
string data = System.IO.File.ReadAllText(P8_PATH);
List<string> list = data.Split('\n').ToList();
parameters = parameters ?? new List<Tuple<string, string>>();
string prk = list.Where((s, i) => i != 0 && i != list.Count - 1).Aggregate((agg, s) => agg + s);
ECDsaCng key = new ECDsaCng(CngKey.Import(Convert.FromBase64String(prk), CngKeyBlobFormat.Pkcs8PrivateBlob));
string token = GetProviderToken();
string url = string.Format(WEB_ADDRESS, deviceToken);
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url);
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
httpRequestMessage.Headers.TryAddWithoutValidation("apns-push-type", "alert"); // or background
httpRequestMessage.Headers.TryAddWithoutValidation("apns-id", Guid.NewGuid().ToString("D"));
//Expiry
//
httpRequestMessage.Headers.TryAddWithoutValidation("apns-expiration", Convert.ToString(0));
//Send imediately
httpRequestMessage.Headers.TryAddWithoutValidation("apns-priority", Convert.ToString(10));
//App Bundle
httpRequestMessage.Headers.TryAddWithoutValidation("apns-topic", "com.xxx.yyy");
//Category
httpRequestMessage.Headers.TryAddWithoutValidation("apns-collapse-id", "test");
//
var body = JsonConvert.SerializeObject(new
{
aps = new
{
alert = new
{
title = title,
body = content,
time = DateTime.Now.ToString()
},
badge = 1,
sound = "default"
},
acme2 = new string[] { "bang", "whiz" }
});
httpRequestMessage.Version = new Version(2, 0);
using (var stringContent = new StringContent(body, Encoding.UTF8, "application/json"))
{
//Set Body
httpRequestMessage.Content = stringContent;
Http2Handler.Http2CustomHandler handler = new Http2Handler.Http2CustomHandler();
handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls;
//handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
//Continue
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage resp = await client.SendAsync(httpRequestMessage).ContinueWith(responseTask =>
{
return responseTask.Result;
});
if (resp != null)
{
string apnsResponseString = await resp.Content.ReadAsStringAsync();
handler.Dispose();
}
handler.Dispose();
}
}
}
catch (Exception ex)
{
success = false;
}
return success;
}
private string GetProviderToken()
{
double epochNow = (int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
Dictionary<string, object> payload = new Dictionary<string, object>()
{
{ "iss", "YOUR APPLE TEAM ID" },
{ "iat", epochNow }
};
var extraHeaders = new Dictionary<string, object>()
{
{ "kid", "YOUR AUTH KEY ID" },
{ "alg", "ES256" }
};
CngKey privateKey = GetPrivateKey();
return JWT.Encode(payload, privateKey, JwsAlgorithm.ES256, extraHeaders);
}
private CngKey GetPrivateKey()
{
using (var reader = File.OpenText(P8_PATH))
{
ECPrivateKeyParameters ecPrivateKeyParameters = (ECPrivateKeyParameters)new PemReader(reader).ReadObject();
var x = ecPrivateKeyParameters.Parameters.G.AffineXCoord.GetEncoded();
var y = ecPrivateKeyParameters.Parameters.G.AffineYCoord.GetEncoded();
var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned();
return EccKey.New(x, y, d);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
其次,如果您注意到的话,我正在使用自定义 WinHTTPHandler 来使代码基于如何使 .net HttpClient 使用 http 2.0? 来支持 HTTP/2?。我正在使用另一个类库创建它,请记住从 Nuget 下载 WinHTTPHandler。
public class Http2CustomHandler : WinHttpHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Version = new Version("2.0");
return base.SendAsync(request, cancellationToken);
}
}
Run Code Online (Sandbox Code Playgroud)
之后,只需调用ApplePushNotificationPush类上的“ SendNotification ” ,您就应该在 iPhone 上收到消息。
| 归档时间: |
|
| 查看次数: |
2524 次 |
| 最近记录: |