C# Web API 方法 returns 403 禁止访问
C# Web API method returns 403 Forbidden
解决了!!! - 查看上次编辑。
在我的 MVC 应用程序中,我使用 HMAC 身份验证 Filterign 调用 Web API 服务。我的 Get (GetMultipleItemsRequest) 有效,但我的 Post 无效。如果我关闭 HMAC 身份验证过滤,它们都可以工作。我不确定为什么 POSTS 不起作用,但 GET 起作用。
我像这样从我的代码中进行 GET 调用(这个有效):
var productsClient = new RestClient<Role>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
var getManyResult = productsClient.GetMultipleItemsRequest("api/Role").Result;
我像这样从我的代码中进行 POST 调用(此代码仅在我关闭 HMAC 时有效):
private RestClient<Profile> profileClient = new RestClient<Profile>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
[HttpPost]
public ActionResult ProfileImport(IEnumerable<HttpPostedFileBase> files)
{
//...
var postResult = profileClient.PostRequest("api/Profile", newProfile).Result;
}
我的 RestClient 构建如下:
public class RestClient<T> where T : class
{
//...
private void SetupClient(HttpClient client, string methodName, string apiUrl, T content = null)
{
const string secretTokenName = "SecretToken";
client.BaseAddress = new Uri(_baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (_hmacSecret)
{
client.DefaultRequestHeaders.Date = DateTime.UtcNow;
var datePart = client.DefaultRequestHeaders.Date.Value.UtcDateTime.ToString(CultureInfo.InvariantCulture);
var fullUri = _baseAddress + apiUrl;
var contentMD5 = "";
if (content != null)
{
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json); // <--- Javascript serialized version is hashed
}
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
var hmac = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
client.DefaultRequestHeaders.Add(secretTokenName, hmac);
}
else if (!string.IsNullOrWhiteSpace(_sharedSecretName))
{
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
client.DefaultRequestHeaders.Add(secretTokenName, sharedSecretValue);
}
}
public async Task<T[]> GetMultipleItemsRequest(string apiUrl)
{
T[] result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "GET", apiUrl);
var response = await client.GetAsync(apiUrl).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T[]>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
public async Task<T> PostRequest(string apiUrl, T postObject)
{
T result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "POST", apiUrl, postObject);
var response = await client.PostAsync(apiUrl, postObject, new JsonMediaTypeFormatter()).ConfigureAwait(false); //<--- not javascript formatted
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
//...
}
我的网站API控制器是这样定义的:
[SecretAuthenticationFilter(SharedSecretName = "xxxxxxxxxxxxxxx", HmacSecret = true)]
public class ProfileController : ApiController
{
[HttpPost]
[ResponseType(typeof(Profile))]
public IHttpActionResult PostProfile(Profile Profile)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
GuidValue = Guid.NewGuid();
Resource res = new Resource();
res.ResourceId = GuidValue;
var data23 = Resourceservices.Insert(res);
Profile.ProfileId = data23.ResourceId;
_profileservices.Insert(Profile);
return CreatedAtRoute("DefaultApi", new { id = Profile.ProfileId }, Profile);
}
}
以下是 SecretAuthenticationFilter 的部分功能:
//now try to read the content as string
string content = actionContext.Request.Content.ReadAsStringAsync().Result;
var contentMD5 = content == "" ? "" : Hashing.GetHashMD5OfString(content); //<-- Hashing the non-JavaScriptSerialized
var datePart = "";
var requestDate = DateTime.Now.AddDays(-2);
if (actionContext.Request.Headers.Date != null)
{
requestDate = actionContext.Request.Headers.Date.Value.UtcDateTime;
datePart = requestDate.ToString(CultureInfo.InvariantCulture);
}
var methodName = actionContext.Request.Method.Method;
var fullUri = actionContext.Request.RequestUri.ToString();
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var expectedValue = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
// Are the hmacs the same, and have we received it within +/- 5 mins (sending and
// receiving servers may not have exactly the same time)
if (messageSecretValue == expectedValue
&& requestDate > DateTime.UtcNow.AddMinutes(-5)
&& requestDate < DateTime.UtcNow.AddMinutes(5))
goodRequest = true;
知道为什么 HMAC 不适用于 POST 吗?
编辑:
当 SecretAuthenticationFilter 尝试将发送的 HMAC 与它认为 HMAC 应该是的内容进行比较时,它们不匹配。原因是内容的 MD5Hash 与接收到的内容的 MD5Hash 不匹配。 RestClient 使用内容的 JavaScriptSerializer.Serialized 版本对内容进行哈希处理,但随后 PostRequest 将对象作为 JsonMediaTypeFormatted 传递。
这两种类型的格式不同。例如,JavaScriptSerializer 给我们这样的日期:
\"EnteredDate\":\"\/日期(1434642998639)\/\"
传递的内容有这样的日期:
\"EnteredDate\":\"2015-06-18T11:56:38.6390407-04:00\"
我想我需要哈希来使用传递的相同数据,以便另一端的过滤器可以正确确认它。想法?
编辑:
找到了答案,我需要使用此行更改 SetupClient 代码:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
要使用这个:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
现在发送的内容(通过 JSON 格式化)将匹配散列的内容。
我不是最初编写此代码的人。 :)
找到答案,我需要更改 SetupClient 代码以防止使用此行:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
要使用这个:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
现在用于散列的内容将被格式化为 JSON 并将匹配发送的内容(也通过 JSON 格式化)。
解决了!!! - 查看上次编辑。
在我的 MVC 应用程序中,我使用 HMAC 身份验证 Filterign 调用 Web API 服务。我的 Get (GetMultipleItemsRequest) 有效,但我的 Post 无效。如果我关闭 HMAC 身份验证过滤,它们都可以工作。我不确定为什么 POSTS 不起作用,但 GET 起作用。
我像这样从我的代码中进行 GET 调用(这个有效):
var productsClient = new RestClient<Role>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
var getManyResult = productsClient.GetMultipleItemsRequest("api/Role").Result;
我像这样从我的代码中进行 POST 调用(此代码仅在我关闭 HMAC 时有效):
private RestClient<Profile> profileClient = new RestClient<Profile>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
[HttpPost]
public ActionResult ProfileImport(IEnumerable<HttpPostedFileBase> files)
{
//...
var postResult = profileClient.PostRequest("api/Profile", newProfile).Result;
}
我的 RestClient 构建如下:
public class RestClient<T> where T : class
{
//...
private void SetupClient(HttpClient client, string methodName, string apiUrl, T content = null)
{
const string secretTokenName = "SecretToken";
client.BaseAddress = new Uri(_baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (_hmacSecret)
{
client.DefaultRequestHeaders.Date = DateTime.UtcNow;
var datePart = client.DefaultRequestHeaders.Date.Value.UtcDateTime.ToString(CultureInfo.InvariantCulture);
var fullUri = _baseAddress + apiUrl;
var contentMD5 = "";
if (content != null)
{
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json); // <--- Javascript serialized version is hashed
}
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
var hmac = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
client.DefaultRequestHeaders.Add(secretTokenName, hmac);
}
else if (!string.IsNullOrWhiteSpace(_sharedSecretName))
{
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
client.DefaultRequestHeaders.Add(secretTokenName, sharedSecretValue);
}
}
public async Task<T[]> GetMultipleItemsRequest(string apiUrl)
{
T[] result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "GET", apiUrl);
var response = await client.GetAsync(apiUrl).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T[]>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
public async Task<T> PostRequest(string apiUrl, T postObject)
{
T result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "POST", apiUrl, postObject);
var response = await client.PostAsync(apiUrl, postObject, new JsonMediaTypeFormatter()).ConfigureAwait(false); //<--- not javascript formatted
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
//...
}
我的网站API控制器是这样定义的:
[SecretAuthenticationFilter(SharedSecretName = "xxxxxxxxxxxxxxx", HmacSecret = true)]
public class ProfileController : ApiController
{
[HttpPost]
[ResponseType(typeof(Profile))]
public IHttpActionResult PostProfile(Profile Profile)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
GuidValue = Guid.NewGuid();
Resource res = new Resource();
res.ResourceId = GuidValue;
var data23 = Resourceservices.Insert(res);
Profile.ProfileId = data23.ResourceId;
_profileservices.Insert(Profile);
return CreatedAtRoute("DefaultApi", new { id = Profile.ProfileId }, Profile);
}
}
以下是 SecretAuthenticationFilter 的部分功能:
//now try to read the content as string
string content = actionContext.Request.Content.ReadAsStringAsync().Result;
var contentMD5 = content == "" ? "" : Hashing.GetHashMD5OfString(content); //<-- Hashing the non-JavaScriptSerialized
var datePart = "";
var requestDate = DateTime.Now.AddDays(-2);
if (actionContext.Request.Headers.Date != null)
{
requestDate = actionContext.Request.Headers.Date.Value.UtcDateTime;
datePart = requestDate.ToString(CultureInfo.InvariantCulture);
}
var methodName = actionContext.Request.Method.Method;
var fullUri = actionContext.Request.RequestUri.ToString();
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var expectedValue = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
// Are the hmacs the same, and have we received it within +/- 5 mins (sending and
// receiving servers may not have exactly the same time)
if (messageSecretValue == expectedValue
&& requestDate > DateTime.UtcNow.AddMinutes(-5)
&& requestDate < DateTime.UtcNow.AddMinutes(5))
goodRequest = true;
知道为什么 HMAC 不适用于 POST 吗?
编辑:
当 SecretAuthenticationFilter 尝试将发送的 HMAC 与它认为 HMAC 应该是的内容进行比较时,它们不匹配。原因是内容的 MD5Hash 与接收到的内容的 MD5Hash 不匹配。 RestClient 使用内容的 JavaScriptSerializer.Serialized 版本对内容进行哈希处理,但随后 PostRequest 将对象作为 JsonMediaTypeFormatted 传递。
这两种类型的格式不同。例如,JavaScriptSerializer 给我们这样的日期: \"EnteredDate\":\"\/日期(1434642998639)\/\"
传递的内容有这样的日期: \"EnteredDate\":\"2015-06-18T11:56:38.6390407-04:00\"
我想我需要哈希来使用传递的相同数据,以便另一端的过滤器可以正确确认它。想法?
编辑: 找到了答案,我需要使用此行更改 SetupClient 代码:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
要使用这个:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
现在发送的内容(通过 JSON 格式化)将匹配散列的内容。
我不是最初编写此代码的人。 :)
找到答案,我需要更改 SetupClient 代码以防止使用此行:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
要使用这个:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
现在用于散列的内容将被格式化为 JSON 并将匹配发送的内容(也通过 JSON 格式化)。