如何保护ASP.NET Web API
我想用第三方开发人员用来访问我的应用程序数据的ASP.NET Web API构建一个RESTful Web服务。
我已经阅读了很多关于OAuth的文章 ,它似乎是标准,但是找到一个很好的样本,并附有说明它如何工作的文档(实际上它确实有用!)似乎非常困难(特别是对于OAuth的新手来说)。
是否有一个实际构建和运行的示例,并展示如何实现?
我已经下载了大量样本:
我也看过博客提出一个简单的基于令牌的方案(像这样) - 这看起来像重新发明轮子,但它确实具有概念上相当简单的优点。
似乎在SO上有这样的问题,但没有很好的答案。
大家在这个领域做什么?
更新:
对于任何对JWT感兴趣的人,我已经把我的另一个答案放在这里,如何在这里为Web API使用JWT身份验证:
用于Asp.Net Web Api的JWT认证
我们已经成功应用HMAC身份验证来保护Web API,并且它工作正常。 HMAC认证为消费者和服务器都知道的每个消费者使用一个秘密密钥来hmac散列消息,应该使用HMAC256。 大多数情况下,消费者的密码被用作秘密密钥。
该消息通常是根据HTTP请求中的数据构建的,或者甚至是添加到HTTP标头的自定义数据,该消息可能包括:
在引擎盖下,HMAC认证将是:
在构建HTTP请求的模板签名(hmac hash输出)之后,使用者向Web服务器发送HTTP请求:
User-Agent: {agent}
Host: {host}
Timestamp: {timestamp}
Authentication: {username}:{signature}
GET请求示例:
GET /webapi.hmac/api/values
User-Agent: Fiddler
Host: localhost
Timestamp: Thursday, August 02, 2012 3:30:32 PM
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=
要散列以获取签名的消息:
GETn
Thursday, August 02, 2012 3:30:32 PMn
/webapi.hmac/api/valuesn
具有查询字符串的POST请求示例(下面的签名不正确,只是一个示例)
POST /webapi.hmac/api/values?key2=value2
User-Agent: Fiddler
Host: localhost
Content-Type: application/x-www-form-urlencoded
Timestamp: Thursday, August 02, 2012 3:30:32 PM
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=
key1=value1&key3=value3
要散列以获取签名的消息
GETn
Thursday, August 02, 2012 3:30:32 PMn
/webapi.hmac/api/valuesn
key1=value1&key2=value2&key3=value3
请注意,表单数据和查询字符串应该按顺序排列,以便服务器上的代码获取查询字符串和表单数据以生成正确的消息。
当HTTP请求到达服务器时,实现一个认证动作过滤器来解析获取信息的请求:HTTP动词,timestamp,uri,表单数据和查询字符串,然后根据这些信息构建签名(使用hmac hash)密钥(哈希密码)在服务器上。
密钥是从请求中的用户名和数据库中获得的。
然后服务器代码将请求中的签名与构建的签名进行比较; 如果相等,则认证通过,否则认证失败。
构建签名的代码:
private static string ComputeHash(string hashedPassword, string message)
{
var key = Encoding.UTF8.GetBytes(hashedPassword.ToUpper());
string hashString;
using (var hmac = new HMACSHA256(key))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
hashString = Convert.ToBase64String(hash);
}
return hashString;
}
那么,如何防止重播攻击?
为时间戳添加约束,如下所示:
servertime - X minutes|seconds <= timestamp <= servertime + X minutes|seconds
(servertime:请求到服务器的时间)
并且,将请求的签名缓存在内存中(使用MemoryCache,应该保持时间限制)。 如果下一个请求带有与前一个请求相同的签名,它将被拒绝。
演示代码放在这里:https://github.com/cuongle/Hmac.WebApi
我建议先从最直接的解决方案开始 - 也许简单的HTTP基本身份验证+ HTTPS就足够了。
如果没有(例如你不能使用https,或者需要更复杂的密钥管理),你可以看看其他人建议的基于HMAC的解决方案。 这种API的一个很好的例子就是Amazon S3(http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html)
我在ASP.NET Web API中撰写了一篇关于基于HMAC的身份验证的博客文章。 它讨论了Web API服务和Web API客户端,代码在bitbucket上可用。 http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/
以下是关于Web API中基本身份验证的文章:http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/
请记住,如果您要为第三方提供API,您也很可能负责提供客户端库。 基本身份验证在这里具有显着的优势,因为它在大多数开箱即用的编程平台上得到支持。 另一方面,HMAC并不是标准化的,需要定制实施。 这些应该是相对直接的,但仍然需要工作。
PS。 还有一个使用HTTPS +证书的选项。 http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/
你有没有试过DevDefined.OAuth?
我用它来确保我的WebApi采用2腿OAuth。 我也用PHP客户端成功测试过它。
使用这个库来添加对OAuth的支持是很容易的。 以下介绍如何实现ASP.NET MVC Web API的提供者:
1)获取DevDefined.OAuth的源代码:https://github.com/bittercoder/DevDefined.OAuth - 最新版本支持OAuthContextBuilder
扩展性。
2)构建库并在您的Web API项目中引用它。
3)创建一个自定义上下文构建器来支持从HttpRequestMessage
构建上下文:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Web;
using DevDefined.OAuth.Framework;
public class WebApiOAuthContextBuilder : OAuthContextBuilder
{
public WebApiOAuthContextBuilder()
: base(UriAdjuster)
{
}
public IOAuthContext FromHttpRequest(HttpRequestMessage request)
{
var context = new OAuthContext
{
RawUri = this.CleanUri(request.RequestUri),
Cookies = this.CollectCookies(request),
Headers = ExtractHeaders(request),
RequestMethod = request.Method.ToString(),
QueryParameters = request.GetQueryNameValuePairs()
.ToNameValueCollection(),
};
if (request.Content != null)
{
var contentResult = request.Content.ReadAsByteArrayAsync();
context.RawContent = contentResult.Result;
try
{
// the following line can result in a NullReferenceException
var contentType =
request.Content.Headers.ContentType.MediaType;
context.RawContentType = contentType;
if (contentType.ToLower()
.Contains("application/x-www-form-urlencoded"))
{
var stringContentResult = request.Content
.ReadAsStringAsync();
context.FormEncodedParameters =
HttpUtility.ParseQueryString(stringContentResult.Result);
}
}
catch (NullReferenceException)
{
}
}
this.ParseAuthorizationHeader(context.Headers, context);
return context;
}
protected static NameValueCollection ExtractHeaders(
HttpRequestMessage request)
{
var result = new NameValueCollection();
foreach (var header in request.Headers)
{
var values = header.Value.ToArray();
var value = string.Empty;
if (values.Length > 0)
{
value = values[0];
}
result.Add(header.Key, value);
}
return result;
}
protected NameValueCollection CollectCookies(
HttpRequestMessage request)
{
IEnumerable<string> values;
if (!request.Headers.TryGetValues("Set-Cookie", out values))
{
return new NameValueCollection();
}
var header = values.FirstOrDefault();
return this.CollectCookiesFromHeaderString(header);
}
/// <summary>
/// Adjust the URI to match the RFC specification (no query string!!).
/// </summary>
/// <param name="uri">
/// The original URI.
/// </param>
/// <returns>
/// The adjusted URI.
/// </returns>
private static Uri UriAdjuster(Uri uri)
{
return
new Uri(
string.Format(
"{0}://{1}{2}{3}",
uri.Scheme,
uri.Host,
uri.IsDefaultPort ?
string.Empty :
string.Format(":{0}", uri.Port),
uri.AbsolutePath));
}
}
4)使用本教程创建OAuth提供程序:http://code.google.com/p/devdefined-tools/wiki/OAuthProvider。 在最后一步(访问受保护的资源示例)中,您可以在您的AuthorizationFilterAttribute
属性中使用此代码:
public override void OnAuthorization(HttpActionContext actionContext)
{
// the only change I made is use the custom context builder from step 3:
OAuthContext context =
new WebApiOAuthContextBuilder().FromHttpRequest(actionContext.Request);
try
{
provider.AccessProtectedResourceRequest(context);
// do nothing here
}
catch (OAuthException authEx)
{
// the OAuthException's Report property is of the type "OAuthProblemReport", it's ToString()
// implementation is overloaded to return a problem report string as per
// the error reporting OAuth extension: http://wiki.oauth.net/ProblemReporting
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
RequestMessage = request, ReasonPhrase = authEx.Report.ToString()
};
}
}
我已经实现了我自己的提供程序,所以我没有测试上述代码(当然除了我在提供程序中使用的WebApiOAuthContextBuilder
),但它应该可以正常工作。
上一篇: How to secure an ASP.NET Web API
下一篇: Is there an equivalent to ASP.NET Web API in the Rails world?