Решено !!! - См. Последнее редактирование.C# API API возвращает 403 Запрещено
В моем приложении MVC я звоню в службу веб-API с помощью HFAC Authentication Filterign. Мой Get (GetMultipleItemsRequest) работает, но мой пост не работает. Если я отключу фильтрацию подлинности 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;
}
//...
}
My Web 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?
EDIT:
Когда SecretAuthenticationFilter пытается сравнить отправленное HMAC, с тем, что, по его мнению, HMAC должно быть, они не совпадают. Причина в том, что MD5Hash содержимого не соответствует MD5Hash полученного контента. RestClient хэширует содержимое с помощью JavaScriptSerializer.Serialized версии содержимого, но затем PostRequest передает объект как JsonMediaTypeFormatted.
Эти два типа не отформатированы одинаково. Например, JavaScriptSerializer дают нам мы датирует так: \ "EnteredDate \": \ "\/Date (1434642998639) \/\"
Передаваемый контент имеет даты, как это: \ "EnteredDate \": \ "2015-06-18T11: 56: 38.6390407-04: 00 \"
Я думаю, мне нужен хэш для использования тех же данных, которые были переданы, поэтому фильтр на другом конце может подтвердить его правильно. Мысли?
EDIT: Найден ответ, мне нужно, чтобы изменить код SetupClient с помощью этой строки:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Для использования этого:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Теперь отправленное содержание (форматированный с помощью JSON) будет соответствовать хешированному контенту.
Я не был тем человеком, который написал этот код изначально.:)
Проверьте, установлен ли модуль WebDav. Если это так, удалите его и повторите попытку. – Alioza