如何从 C# 连接到 Apple News API?
How do I connect to the Apple News API from c#?
我正在尝试连接到 Apple 的新闻 API。生成签名时,每个不同示例的结果看起来都一样。但是,我不断收到此错误:
{"errors":[{"code":"WRONG_SIGNATURE"}]}
这是我生成身份验证的 C# 代码 header。
public class Security
{
public static string AuthHeader(string method, string url, object content=null)
{
var apiKeyId = "<YOUR KEY HERE...>"; //we get this value from our settings file.
var apiKeySecret = "<YOUR SECRET HERE...>"; //we get this value from our settings file.
if ( string.IsNullOrEmpty(apiKeyId) || string.IsNullOrEmpty(apiKeySecret)) return string.Empty;
var encoding = new ASCIIEncoding();
var dt = DateTime.Now.ToString(Constants.DateFormat);
var canonicalRequest = string.Format("{0}{1}{2}", method, url, dt);
var key = Convert.FromBase64String(apiKeySecret);
var hmac = new HMACSHA256(key);
var hashed = hmac.ComputeHash(encoding.GetBytes(canonicalRequest));
var signature = Convert.ToBase64String(hashed);
var authorizaton = string.Format(@"HHMAC; key={0}; signature={1}; date={2}", apiKeyId, signature, dt);
return authorizaton;
}
}
常量的简短版本class
public static class Constants
{
public static readonly string ChannelId = "<YOUR CHANNEL ID HERE...>"; //again from our settings file
public static readonly string BaseUrl = "https://news-api.apple.com";
public static readonly string DateFormat = "yyyy-MM-ddTHH:mm:ssK";
}
Actions 的简短版本class(SendCommand 是执行请求的方法)
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}
我正在使用 ReadChannel 方法进行测试。
我也尝试过 php 和 ruby 中的示例,但没有成功。
知道如何正确执行此操作吗?
错误抱怨我们计算签名的方式有问题。让我们看一下Apple生成正确签名的示例代码,它在这里:
https://developer.apple.com/documentation/apple_news/apple_news_api/about_the_news_security_model
不幸的是我只找到了Python代码。我也不知道 Python,但我可以想出足够的办法来调整它以仅显示签名。我们还需要确切知道使用的日期值。
import base64
from hashlib import sha256
import hmac
from datetime import datetime
channel_id = 'cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_id = '240ab880-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_secret = 'HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF='
url = 'https://news-api.apple.com/channels/%s' % channel_id
date = str(datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"))
canonical_request = 'GET' + url + date
key = base64.b64decode(api_key_secret)
hashed = hmac.new(key, canonical_request, sha256)
signature = hashed.digest().encode("base64").rstrip('\n')
print date
print signature
你可以在这里看到结果(短link指向tutorialspoint.com,这是我在Google找到的第一个在线python解释器:
如果您不信任 link,请知道我能够使用它根据这些已知输入计算出以下已知正确的签名:
方法: GET
URL: https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF
日期时间: 2018-06-12T18:15:45Z
API 秘密: HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=
签名: f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=
现在我们可以编写可以验证的 C# 代码了。当我们可以使用这些相同的输入来产生相同的签名结果时,我们就会得到正确的代码。
基于此,我能够编写此 C# 代码:
public static void Main()
{
string method = "GET";
string url = "https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF";
string dateString = "2018-06-12T18:15:45Z";
string apiKeySecret = "HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=";
var MyResult = GetSignature(method, url, dateString, apiKeySecret);
var DocumentedResult = "f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=";
Console.WriteLine(MyResult);
Console.WriteLine(MyResult == DocumentedResult);
}
public static string GetSignature(string method, string url, string dt, string APISecret)
{
var hmac = new HMACSHA256(Convert.FromBase64String(APISecret));
var hashed = hmac.ComputeHash(Encoding.ASCII.GetBytes(method + url + dt));
return Convert.ToBase64String(hashed);
}
您可以在此处看到实际效果:
我没有自己的 Apple API 密钥来进行测试,所以我只能为您提供这些。
我从问题中注意到的一件事是 Apple 的示例在日期字符串的末尾有一个“Z
”,原始代码在这里丢失了。
将此 post 上的原始代码生成的授权字符串粘贴到 fiddler 中,我能够从 Apple News API 获得成功响应。似乎 HttpWebRequest 没有正确包含授权 header 并且使用 属性 PreAuthenticate = true 提交相同的请求更正了这个问题 (HttpWebRequest.PreAuthenticate)。此外,对于 GET 请求,需要省略 ContentType,因此我添加了一个条件语句来说明这一点。
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.PreAuthenticate = true;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
if(method.Equals("post", StringComparison.InvariantCultureIgnoreCase))
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}
我正在尝试连接到 Apple 的新闻 API。生成签名时,每个不同示例的结果看起来都一样。但是,我不断收到此错误:
{"errors":[{"code":"WRONG_SIGNATURE"}]}
这是我生成身份验证的 C# 代码 header。
public class Security
{
public static string AuthHeader(string method, string url, object content=null)
{
var apiKeyId = "<YOUR KEY HERE...>"; //we get this value from our settings file.
var apiKeySecret = "<YOUR SECRET HERE...>"; //we get this value from our settings file.
if ( string.IsNullOrEmpty(apiKeyId) || string.IsNullOrEmpty(apiKeySecret)) return string.Empty;
var encoding = new ASCIIEncoding();
var dt = DateTime.Now.ToString(Constants.DateFormat);
var canonicalRequest = string.Format("{0}{1}{2}", method, url, dt);
var key = Convert.FromBase64String(apiKeySecret);
var hmac = new HMACSHA256(key);
var hashed = hmac.ComputeHash(encoding.GetBytes(canonicalRequest));
var signature = Convert.ToBase64String(hashed);
var authorizaton = string.Format(@"HHMAC; key={0}; signature={1}; date={2}", apiKeyId, signature, dt);
return authorizaton;
}
}
常量的简短版本class
public static class Constants
{
public static readonly string ChannelId = "<YOUR CHANNEL ID HERE...>"; //again from our settings file
public static readonly string BaseUrl = "https://news-api.apple.com";
public static readonly string DateFormat = "yyyy-MM-ddTHH:mm:ssK";
}
Actions 的简短版本class(SendCommand 是执行请求的方法)
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}
我正在使用 ReadChannel 方法进行测试。
我也尝试过 php 和 ruby 中的示例,但没有成功。
知道如何正确执行此操作吗?
错误抱怨我们计算签名的方式有问题。让我们看一下Apple生成正确签名的示例代码,它在这里:
https://developer.apple.com/documentation/apple_news/apple_news_api/about_the_news_security_model
不幸的是我只找到了Python代码。我也不知道 Python,但我可以想出足够的办法来调整它以仅显示签名。我们还需要确切知道使用的日期值。
import base64
from hashlib import sha256
import hmac
from datetime import datetime
channel_id = 'cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_id = '240ab880-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
api_key_secret = 'HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF='
url = 'https://news-api.apple.com/channels/%s' % channel_id
date = str(datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"))
canonical_request = 'GET' + url + date
key = base64.b64decode(api_key_secret)
hashed = hmac.new(key, canonical_request, sha256)
signature = hashed.digest().encode("base64").rstrip('\n')
print date
print signature
你可以在这里看到结果(短link指向tutorialspoint.com,这是我在Google找到的第一个在线python解释器:
如果您不信任 link,请知道我能够使用它根据这些已知输入计算出以下已知正确的签名:
方法: GET
URL: https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF
日期时间: 2018-06-12T18:15:45Z
API 秘密: HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=
签名: f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=
现在我们可以编写可以验证的 C# 代码了。当我们可以使用这些相同的输入来产生相同的签名结果时,我们就会得到正确的代码。
基于此,我能够编写此 C# 代码:
public static void Main()
{
string method = "GET";
string url = "https://news-api.apple.com/channels/cdb737aa-FFFF-FFFF-FFFF-FFFFFFFFFFFF";
string dateString = "2018-06-12T18:15:45Z";
string apiKeySecret = "HgyfMPjFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=";
var MyResult = GetSignature(method, url, dateString, apiKeySecret);
var DocumentedResult = "f3cOzwH7HGYPg481noBFwKgVOGAhH3jy7LQ75jVignA=";
Console.WriteLine(MyResult);
Console.WriteLine(MyResult == DocumentedResult);
}
public static string GetSignature(string method, string url, string dt, string APISecret)
{
var hmac = new HMACSHA256(Convert.FromBase64String(APISecret));
var hashed = hmac.ComputeHash(Encoding.ASCII.GetBytes(method + url + dt));
return Convert.ToBase64String(hashed);
}
您可以在此处看到实际效果:
我没有自己的 Apple API 密钥来进行测试,所以我只能为您提供这些。
我从问题中注意到的一件事是 Apple 的示例在日期字符串的末尾有一个“Z
”,原始代码在这里丢失了。
将此 post 上的原始代码生成的授权字符串粘贴到 fiddler 中,我能够从 Apple News API 获得成功响应。似乎 HttpWebRequest 没有正确包含授权 header 并且使用 属性 PreAuthenticate = true 提交相同的请求更正了这个问题 (HttpWebRequest.PreAuthenticate)。此外,对于 GET 请求,需要省略 ContentType,因此我添加了一个条件语句来说明这一点。
public class Actions
{
public static string SendCommand(string action, string method)
{
var url = $"{Constants.BaseUrl}{action}";
var authheader = Security.AuthHeader(method, url, null);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Timeout = 1000;
request.PreAuthenticate = true;
request.Headers.Add("Authorization", authheader);
request.Accept = "application/json";
if(method.Equals("post", StringComparison.InvariantCultureIgnoreCase))
request.ContentType = "application/json";
var output = string.Empty;
try
{
using (var response = request.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
output = reader.ReadToEnd();
}
}
catch (WebException e)
{
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
public static string ReadChannel()
{
var action = $"/channels/{Constants.ChannelId}";
const string method = "GET";
return SendCommand(action, method);
}
}