Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/264.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在C#中实现基于apple令牌的推送通知(使用p8文件)?_C#_Apple Push Notifications - Fatal编程技术网

如何在C#中实现基于apple令牌的推送通知(使用p8文件)?

如何在C#中实现基于apple令牌的推送通知(使用p8文件)?,c#,apple-push-notifications,C#,Apple Push Notifications,对于具有某种基于聊天功能的应用程序,我想添加推送通知支持以接收新消息。 我想做的是使用Apple提供的新的基于令牌的身份验证(.p8文件),但是我找不到关于服务器部分的更多信息 我看到了以下帖子: 然而,答案并不令人满意,因为关于如何: 与APN建立连接 使用p8文件(某种编码除外) 向Apple推送通知服务发送数据 您可以使用PushSharp,这是一个nuget软件包,支持苹果、谷歌和微软的推送通知 以下是和的链接 这是为Apple发送推送通知的示例: // Configuration

对于具有某种基于聊天功能的应用程序,我想添加推送通知支持以接收新消息。 我想做的是使用Apple提供的新的基于令牌的身份验证(.p8文件),但是我找不到关于服务器部分的更多信息

我看到了以下帖子:

然而,答案并不令人满意,因为关于如何:

  • 与APN建立连接
  • 使用p8文件(某种编码除外)
  • 向Apple推送通知服务发送数据

您可以使用
PushSharp
,这是一个nuget软件包,支持苹果、谷歌和微软的推送通知

以下是和的链接

这是为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 ();

目前,您无法在原始的.NET框架上真正做到这一点。新的基于JWT的APNS服务器只使用HTTP/2,而.NET Framework还不支持HTTP/2

但是,只要满足以下先决条件,.NET Core版本的
System.NET.Http
,就可以:

  • 在Windows上,您必须运行Windows 10周年纪念版(v1607)或更高版本,或者Windows Server 2016的等效版本(我认为)
  • 在Linux上,必须具有支持HTTP/2的
    libcurl
    版本
  • 在macOS上,您必须编译支持HTTP/2的
    libcurl
    ,然后使用
    DYLD\u INSERT\u库
    环境变量来加载自定义的
    libcurl
如果确实需要,您应该能够在.NET Framework中使用.NET Core版本的
System.NET.Http

我不知道Mono、Xamarin或UWP会发生什么

然后,您必须做三件事:

  • 解析提供给您的私钥。这是当前的ECDSA密钥,您可以将其加载到
    System.Security.Cryptography.ECDSA
    对象中
    • 在Windows上,您可以使用CNG API。解析密钥文件的base64编码DER部分后,您可以使用
      新建ECDsaCng(CngKey.Import(data,CngKeyBlobFormat.Pkcs8PrivateBlob))
      创建一个密钥
    • 在macOS或Linux上没有受支持的API,您必须自己解析DER结构,或者使用第三方库
  • 创建JSON Web令牌/承载令牌。如果使用NuGet的
    System.IdentityModel.Tokens.Jwt
    包,这相当简单。您需要Apple提供的密钥ID和团队ID
  • 发送HTTP/2请求。这是正常的,但您需要做两件额外的事情:
  • yourRequestMessage.Version
    设置为
    新版本(2,0)
    ,以便使用HTTP/2发出请求
  • yourRequestMessage.Headers.Authorization
    设置为
    new AuthenticationHeaderValue(“承载者”,令牌)
    ,以便为您的请求提供承载者身份验证令牌/JWT

  • 然后将您的JSON放入HTTP请求并将其发送到正确的URL。

    它在ASP.NET CORE 2.1和2.2上尝试了上述方法,但均无效。我总是得到的回答是“收到的消息是意外的或格式不正确的”,启用了HttpVersion20,这让我怀疑http2实现是否具体

    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;
        }
    
    下面是ASP.NET CORE 3.0的工作原理

     var teamId = "YOURTEAMID";
     var keyId = "YOURKEYID";
    
                try
                {
                    //
                    var data = await System.IO.File.ReadAllTextAsync(Path.Combine(_environment.ContentRootPath, "apns/"+config.P8FileName));
                    var list = data.Split('\n').ToList();
                    var prk = list.Where((s, i) => i != 0 && i != list.Count - 1).Aggregate((agg, s) => agg + s);
                    //
                    var key = new ECDsaCng(CngKey.Import(Convert.FromBase64String(prk), CngKeyBlobFormat.Pkcs8PrivateBlob));
                    //
                    var token = CreateToken(key, keyId, teamId);
                    //
                    var deviceToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX";
                    var url = string.Format("https://api.sandbox.push.apple.com/3/device/{0}", deviceToken);
                    var request = new HttpRequestMessage(HttpMethod.Post, url);
                    //
                    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
                    //
    
                    request.Headers.TryAddWithoutValidation("apns-push-type", "alert"); // or background
                    request.Headers.TryAddWithoutValidation("apns-id", Guid.NewGuid().ToString("D"));
                    //Expiry
                    //
                    request.Headers.TryAddWithoutValidation("apns-expiration", Convert.ToString(0));
                    //Send imediately
                    request.Headers.TryAddWithoutValidation("apns-priority", Convert.ToString(10));
                    //App Bundle
                    request.Headers.TryAddWithoutValidation("apns-topic", "com.xx.yy");
                    //Category
                    request.Headers.TryAddWithoutValidation("apns-collapse-id", "test");
    
                    //
                    var body = JsonConvert.SerializeObject(new
                    {
                        aps = new
                        {
                            alert = new
                            {
                                title = "Test",
                                body = "Sample Test APNS",
                                time = DateTime.Now.ToString()
                            },
                            badge = 1,
                            sound = "default"
                        },
                        acme2 = new string[] { "bang", "whiz" }
                    })
                    //
                    request.Version = HttpVersion.Version20;
                    //
                    using (var stringContent = new StringContent(body, Encoding.UTF8, "application/json"))
                    {
                        //Set Body
                        request.Content = stringContent;
                        _logger.LogInformation(request.ToString());
                        //
                        var handler = new HttpClientHandler();
                        //
                        handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
                        //
                        handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
    
                        //Continue
                        using (HttpClient client = new HttpClient(handler))
                        {
                            //
                            HttpResponseMessage resp = await client.SendAsync(request).ContinueWith(responseTask =>
                            {
                                return responseTask.Result;
                                //
    
                            });
                            //
                            _logger.LogInformation(resp.ToString());
                            //
                            if (resp != null)
                            {
                                string apnsResponseString = await resp.Content.ReadAsStringAsync();
                                //
                                handler.Dispose();
                                //ALL GOOD ....
                                return;
                            }
                            //
                            handler.Dispose();
                        }
                    }
                }
                catch (HttpRequestException e)
                {
                    _logger.LogError(5, e.StackTrace, e);
                }
    
    对于CreateToken(),请参考上面yaakov推荐的解决方案,

    ,因为令牌(.p8)APN仅在HTTP/2中工作,因此大多数解决方案仅在.net Core中工作。因为我的项目使用的是.net Framework,所以需要进行一些调整。如果您像我一样使用.net Framework,请继续阅读

    我到处搜索,遇到了几个问题,我设法解决了,并把它们拼凑在一起

    下面是实际工作的APNs类。我为它创建了一个新的类库,并将.P8文件放在类库的AuthKeys文件夹中。记住右键单击.P8文件并将其设置为“始终复制”。参考

    之后,要获取P8文件的位置,请对web项目使用
    AppDomain.CurrentDomain.RelativeSearchPath
    ,或对win应用程序使用
    AppDomain.CurrentDomain.BaseDirectory
    。提及

    要从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);
                    }
                }
            }
        }
    
        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);
            }
        }
    
    使用Jose;
    使用Newtonsoft.Json;
    使用Org.BouncyCastle.Crypto.Parameters;
    使用Org.BouncyCastle.OpenSsl;
    使用安全加密技术;
    使用制度;
    使用System.Collections.Generic;
    使用System.IO;
    使用System.Linq;
    使用System.Net.Http;
    使用System.Net.Http.Header;
    使用System.Security.Cryptography;
    使用系统文本;
    使用System.Threading.Tasks;
    命名空间库
    {
    公共类ApplePushNotificationPush
    {
    //私有常量字符串WEB_地址=”https://api.sandbox.push.apple.com:443/3/device/{0}";
    私有常量字符串WEB_地址=”https://api.push.apple.com:443/3/device/{0}";
    私有字符串P8_PATH=AppDomain.CurrentDomain.RelativeSearchPath+@“\AuthKeys\APNs_AuthKey.P8”;
    公共应用程序PushNotificationPush()
    {
    }
    公共异步任务SendNotification(字符串deviceToken、字符串标题、字符串内容、int-badge=0、列表参数=null)
    {
    布尔成功=真;
    尝试
    {
    字符串数据=System.IO.File.ReadAllText(P8_路径);
    列表=data.Split('\n').ToList();
    参数=参数??新列表();
    字符串prk=list.Where((s,i)=>i!=0&&i!=list.Count-1)。聚合((agg,s)=>agg+s);
    ECDsaCng key=newecdsacng(CngKey.Import(Convert.FromBase64String(prk),CngKeyBlobFormat.Pkcs8PrivateBlob));
    字符串标记=GetProviderToken();
    
        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);
            }
        }