简体   繁体   English

C#Web API方法返回403禁止

[英]C# Web API method returns 403 Forbidden

Solved!!! 解决了!!! - See last edit. -参见最后编辑。

In my MVC app I make calls out to a Web API service with HMAC Authentication Filterign. 在我的MVC应用程序中,我使用HMAC身份验证Filterign调用Web API服务。 My Get (GetMultipleItemsRequest) works, but my Post does not. 我的Get(GetMultipleItemsRequest)有效,但我的Post不起作用。 If I turn off HMAC authentication filtering all of them work. 如果我关闭了HMAC身份验证过滤,那么所有这些都将起作用。 I'm not sure why the POSTS do not work, but the GETs do. 我不确定为什么POST不起作用,但是GET不能起作用。

I make the GET call from my code like this (this one works): 我像这样从我的代码中进行GET调用(这有效):

var productsClient = new RestClient<Role>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
              "xxxxxxxxxxxxxxx", true);

var getManyResult = productsClient.GetMultipleItemsRequest("api/Role").Result;

I make the POST call from my code like this (this one only works when I turn off HMAC): 我从这样的代码中进行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;
}

My RestClient builds like this: 我的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 Controller is defined like this: 我的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);
    }

}

Here is some of what SecretAuthenticationFilter does: 这是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;

Any idea why HMAC doesn't work for the POST? 知道为什么HMAC对POST不起作用吗?

EDIT: 编辑:
When SecretAuthenticationFilter tries to compare the HMAC sent, with what it thinks the HMAC should be they don't match. 当SecretAuthenticationFilter尝试比较发送的HMAC时,它认为HMAC应该是它们不匹配。 The reason is the MD5Hash of the content doesn't match the MD5Hash of the received content. 原因是内容的MD5Hash与接收到的内容的MD5Hash不匹配。 The RestClient hashes the content using a JavaScriptSerializer.Serialized version of the content, but then the PostRequest passes the object as JsonMediaTypeFormatted. RestClient使用内容的JavaScriptSerializer.Serialized版本对内容进行哈希处理,但是PostRequest随后将对象作为JsonMediaTypeFormatted传递。

These two types don't get formatted the same. 这两种类型的格式不同。 For instance, the JavaScriptSerializer give's us dates like this: \\"EnteredDate\\":\\"\\/Date(1434642998639)\\/\\" 例如,JavaScriptSerializer给我们这样的日期:\\“ EnteredDate \\”:\\“ \\ / Date(1434642998639)\\ / \\”

The passed content has dates like this: \\"EnteredDate\\":\\"2015-06-18T11:56:38.6390407-04:00\\" 传递的内容具有以下日期:\\“ EnteredDate \\”:\\“ 2015-06-18T11:56:38.6390407-04:00 \\”

I guess I need the hash to use the same data that's passed, so the Filter on the other end can confirm it correctly. 我想我需要散列来使用传递的相同数据,因此另一端的筛选器可以正确地确认它。 Thoughts? 有什么想法吗?

EDIT: Found the answer, I needed to change the SetupClient code from using this line: 编辑:找到了答案,我需要使用此行更改SetupClient代码:

var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);

To using this: 要使用此功能:

var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);

Now the sent content (formatted via JSON) will match the hashed content. 现在,发送的内容(通过JSON格式化)将与散列的内容匹配。

I was not the person who wrote this code originally. 我不是最初编写此代码的人。 :) :)

Found the answer, I needed to change the SetupClient code from using this line: 找到了答案,我需要使用此行更改SetupClient代码:

var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);

To using this: 要使用此功能:

var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);

Now the content used for the hash will be formatted as JSON and will match the sent content (which is also formatted via JSON). 现在,用于哈希的内容将被格式化为JSON并将与发送的内容匹配(也通过JSON格式化)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM