如何保护ASP.NET Web API

我想使用ASP.NET Web API构建一个RESTful Web服务,第三方开发人员将使用它来访问我的应用程序的数据。

我已经阅读了很多关于OAuth的文章 ,这似乎是标准,但是找到一个很好的示例来说明它是如何工作的(而且实际上也行得通!)似乎是非常困难的(尤其是对于OAuth的新手来说)。

是否有一个实际构建和运行的示例,并展示如何实现?

我已经下载了许多样本:

  • DotNetOAuth – 从新手的角度来看,文档是无望的
  • Thinktecture – 无法建立

我也看过博客提出一个简单的基于令牌的方案(就像这样 ) – 这似乎是重新发明轮子,但它确实具有在概念上相当简单的优点。

似乎有这样的许多问题,但没有好的答案。

大家在这个领域做什么?

更新:

我已经把我的另一个答案如何使用JWT在Web Api的验证在这里对JWT感兴趣的任何人:

用于Asp.Net Web Api的JWT认证


我们已经设法应用HMAC身份验证来保护Web Api,并且它工作正常。 基本上,HMAC认证为消费者和服务器都知道消息的每个消费者使用一个密钥,应该使用HMAC256。 大多数情况下,用户的密码被用作秘密密钥。

消息通常是从HTTP请求中的数据构建的,甚至是添加到HTTP头中的定制数据,消息可能包括:

  1. 时间戳:发送请求的时间(UTC或GMT时间)
  2. HTTP动词:GET,POST,PUT,DELETE。
  3. 发布数据和查询字符串,
  4. 网址

在引擎盖下,HMAC认证将是:

消费者发送一个HTTP请求到Web服务器,在建立签名(hmac hash的输出)之后,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= 

消息散列来获得签名:

 GET\n Thursday, August 02, 2012 3:30:32 PM\n /webapi.hmac/api/values\n 

具有查询字符串的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 

消息散列以获得签名

 GET\n Thursday, August 02, 2012 3:30:32 PM\n /webapi.hmac/api/values\n 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 ),但它应该工作正常。

Web API引入了一个Attribute [Authorize]来提供安全性。 这可以在全局设置(global.asx)

 public static void Register(HttpConfiguration config) { config.Filters.Add(new AuthorizeAttribute()); } 

或每个控制器:

 [Authorize] public class ValuesController : ApiController{ ... 

当然,您的身份验证类型可能会有所不同,您可能需要执行自己的身份验证,如果发生这种情况,您可能会发现有用的从授权属性继承并将其扩展以满足您的要求:

 public class DemoAuthorizeAttribute : AuthorizeAttribute { public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { if (Authorize(actionContext)) { return; } HandleUnauthorizedRequest(actionContext); } protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext) { var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); challengeMessage.Headers.Add("WWW-Authenticate", "Basic"); throw new HttpResponseException(challengeMessage); } private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext) { try { var someCode = (from h in actionContext.Request.Headers where h.Key == "demo" select h.Value.First()).FirstOrDefault(); return someCode == "myCode"; } catch (Exception) { return false; } } } 

在您的控制器中:

 [DemoAuthorize] public class ValuesController : ApiController{ 

以下是WebApi授权的其他自定义实现的链接:

http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/

如果你想保证你的API在服务器到服务器的时尚(没有重定向到网站的双腿认证)。 您可以查看OAuth2客户端凭证授权协议。

https://dev.twitter.com/docs/auth/application-only-auth

我开发了一个库,可以帮助您轻松地将这种支持添加到您的WebAPI。 你可以把它安装成一个NuGet包:

https://nuget.org/packages/OAuth2ClientCredentialsGrant/1.0.0.0

该库的目标是.NET Framework 4.5。

一旦将包添加到项目中,它将在项目的根目录中创建一个自述文件。 您可以查看该自述文件以了解如何配置/使用此软件包。

干杯!

继续@ Cuong Le的回答,我的做法是防止重播攻击

//使用共享私钥(或用户密码)在客户端对Unix时间进行加密

//作为请求头的一部分发送给服务器(WEB API)

//使用共享私钥(或用户密码)解密服务器上的Unix时间(WEB API)

//检查客户端的Unix时间和服务器的Unix时间之间的时间差,不应该大于x秒

//如果User ID / Hash Password是正确的,并且解密的UnixTime在服务器时间的x秒内,那么这是一个有效的请求