C# 如何保护 ASP.NET Web API
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11775594/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to secure an ASP.NET Web API
提问by Craig Shearer
I want to build a RESTfulweb service using ASP.NET Web API that third-party developers will use to access my application's data.
我想使用 ASP.NET Web API构建一个RESTfulWeb 服务,第三方开发人员将使用它来访问我的应用程序数据。
I've read quite a lot about OAuthand it seems to be the standard, but finding a good sample with documentation explaining how it works (and that actually does work!) seems to be incredibly difficult (especially for a newbie to OAuth).
我已经阅读了很多关于OAuth 的书,它似乎是标准,但是找到一个很好的样本并解释它是如何工作的(实际上确实有效!)似乎非常困难(特别是对于 OAuth 的新手)。
Is there a sample that actually builds and works and shows how to implement this?
是否有实际构建和工作并展示如何实现的示例?
I've downloaded numerous samples:
我下载了许多示例:
- DotNetOAuth - documentation is hopeless from a newbie perspective
- Thinktecture - can't get it to build
- DotNetOAuth - 从新手的角度来看文档是无望的
- Thinktecture - 无法构建
I've also looked at blogs suggesting a simple token-based scheme (like this) - this seems like re-inventing the wheel but it does have the advantage of being conceptually fairly simple.
我还看过一些博客,提出了一个简单的基于令牌的方案(像这样)——这似乎是在重新发明轮子,但它确实具有概念上相当简单的优点。
It seems there are many questions like this on SO but no good answers.
似乎在 SO 上有很多这样的问题,但没有好的答案。
What is everybody doing in this space?
每个人都在这个空间做什么?
回答by cuongle
Update:
更新:
I have added this link to my other answer how to use JWT authentication for ASP.NET Web APIhere for anyone interested in JWT.
我已将此链接添加到我的其他答案中如何为 ASP.NET Web API 使用 JWT 身份验证,供对 JWT 感兴趣的任何人使用。
We have managed to apply HMAC authentication to secure Web API, and it worked okay. HMAC authentication uses a secret key for each consumer which both consumer and server both know to hmac hash a message, HMAC256 should be used. Most of the cases, hashed password of the consumer is used as a secret key.
我们已经成功地应用 HMAC 身份验证来保护 Web API,并且工作正常。HMAC 身份验证为每个消费者使用一个秘密密钥,消费者和服务器都知道对消息进行 hmac 散列,应该使用 HMAC256。大多数情况下,消费者的散列密码用作密钥。
The message normally is built from data in the HTTP request, or even customized data which is added to HTTP header, the message might include:
消息通常是根据 HTTP 请求中的数据构建的,甚至是添加到 HTTP 标头中的自定义数据,消息可能包括:
- Timestamp: time that request is sent (UTC or GMT)
- HTTP verb: GET, POST, PUT, DELETE.
- post data and query string,
- URL
- 时间戳:发送请求的时间(UTC 或 GMT)
- HTTP 动词:GET、POST、PUT、DELETE。
- 发布数据和查询字符串,
- 网址
Under the hood, HMAC authentication would be:
在幕后,HMAC 身份验证将是:
Consumer sends a HTTP request to web server, after building the signature (output of hmac hash), the template of HTTP request:
消费者向Web服务器发送HTTP请求,构建签名(hmac hash的输出)后,HTTP请求的模板:
User-Agent: {agent}
Host: {host}
Timestamp: {timestamp}
Authentication: {username}:{signature}
Example for GET request:
GET 请求示例:
GET /webapi.hmac/api/values
User-Agent: Fiddler
Host: localhost
Timestamp: Thursday, August 02, 2012 3:30:32 PM
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=
The message to hash to get signature:
要散列以获取签名的消息:
GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n
Example for POST request with query string (signature below is not correct, just an example)
带有查询字符串的 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
The message to hash to get signature
要散列以获取签名的消息
GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n
key1=value1&key2=value2&key3=value3
Please note that form data and query string should be in order, so the code on the server get query string and form data to build the correct message.
请注意表单数据和查询字符串应该是有序的,所以服务器上的代码获取查询字符串和表单数据以构建正确的消息。
When HTTP request comes to the server, an authentication action filter is implemented to parse the request to get information: HTTP verb, timestamp, uri, form data and query string, then based on these to build signature (use hmac hash) with the secret key (hashed password) on the server.
当 HTTP 请求到达服务器时,会实现一个身份验证动作过滤器来解析请求以获取信息:HTTP 动词、时间戳、uri、表单数据和查询字符串,然后基于这些构建带有密钥的签名(使用 hmac 哈希)服务器上的密钥(散列密码)。
The secret key is got from the database with the username on the request.
密钥是从数据库中获取的,其中包含请求中的用户名。
Then server code compares the signature on the request with the signature built; if equal, authentication is passed, otherwise, it failed.
然后服务器代码将请求上的签名与构建的签名进行比较;如果相等,则认证通过,否则,认证失败。
The code to build signature:
构建签名的代码:
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;
}
So, how to prevent replay attack?
那么,如何防止重放攻击呢?
Add constraint for the timestamp, something like:
为时间戳添加约束,例如:
servertime - X minutes|seconds <= timestamp <= servertime + X minutes|seconds
(servertime: time of request coming to server)
(服务器时间:请求到达服务器的时间)
And, cache the signature of the request in memory (use MemoryCache, should keep in the limit of time). If the next request comes with the same signature with the previous request, it will be rejected.
并且,在内存中缓存请求的签名(使用MemoryCache,应该保持在时间限制内)。如果下一个请求带有与前一个请求相同的签名,它将被拒绝。
The demo code is put as here: https://github.com/cuongle/Hmac.WebApi
演示代码放在这里:https: //github.com/cuongle/Hmac.WebApi
回答by Maksymilian Majer
Have you tried DevDefined.OAuth?
您是否尝试过 DevDefined.OAuth?
I have used it to secure my WebApi with 2-Legged OAuth. I have also successfully tested it with PHP clients.
我已经用它通过 2-Legged OAuth 来保护我的 WebApi。我也成功地用 PHP 客户端对其进行了测试。
It's quite easy to add support for OAuth using this library. Here's how you can implement the provider for ASP.NET MVC Web API:
使用此库添加对 OAuth 的支持非常容易。以下是为 ASP.NET MVC Web API 实现提供程序的方法:
1) Get the source code of DevDefined.OAuth: https://github.com/bittercoder/DevDefined.OAuth- the newest version allows for OAuthContextBuilderextensibility.
1) 获取 DevDefined.OAuth 的源代码:https: //github.com/bittercoder/DevDefined.OAuth- 最新版本允许OAuthContextBuilder扩展。
2) Build the library and reference it in your Web API project.
2) 构建库并在您的 Web API 项目中引用它。
3) Create a custom context builder to support building a context from HttpRequestMessage:
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) Use this tutorial for creating an OAuth provider: http://code.google.com/p/devdefined-tools/wiki/OAuthProvider. In the last step (Accessing Protected Resource Example) you can use this code in your AuthorizationFilterAttributeattribute:
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()
};
}
}
I have implemented my own provider so I haven't tested the above code (except of course the WebApiOAuthContextBuilderwhich I'm using in my provider) but it should work fine.
我已经实现了我自己的提供程序,所以我没有测试上面的代码(当然WebApiOAuthContextBuilder我在我的提供程序中使用的除外 ),但它应该可以正常工作。
回答by Piotr Walat
I would suggest starting with the most straightforward solutions first - maybe simple HTTP Basic Authentication + HTTPS is enough in your scenario.
我建议首先从最直接的解决方案开始 - 也许简单的 HTTP 基本身份验证 + HTTPS 在您的场景中就足够了。
If not (for example you cannot use https, or need more complex key management), you may have a look at HMAC-based solutions as suggested by others. A good example of such API would be Amazon S3 (http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html)
如果不是(例如,您不能使用 https,或者需要更复杂的密钥管理),您可以查看其他人建议的基于 HMAC 的解决方案。这种 API 的一个很好的例子是 Amazon S3 ( http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html)
I wrote a blog post about HMAC based authentication in ASP.NET Web API. It discusses both Web API service and Web API client and the code is available on bitbucket. http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/
我写了一篇关于 ASP.NET Web API 中基于 HMAC 的身份验证的博客文章。它讨论了 Web API 服务和 Web API 客户端,代码在 bitbucket 上可用。http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/
Here is a post about Basic Authentication in Web API: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/
这是关于 Web API 中的基本身份验证的帖子:http: //www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/
Remember that if you are going to provide an API to 3rd parties, you will also most likely be responsible for delivering client libraries. Basic authentication has a significant advantage here as it is supported on most programming platforms out of the box. HMAC, on the other hand, is not that standardized and will require custom implementation. These should be relatively straightforward but still require work.
请记住,如果您要向第 3 方提供 API,您很可能还需要负责交付客户端库。基本身份验证在这里具有显着优势,因为它在大多数开箱即用的编程平台上都受支持。另一方面,HMAC 不是那么标准化,需要自定义实现。这些应该相对简单,但仍然需要工作。
PS. There is also an option to use HTTPS + certificates. http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/
附注。还有一个选项可以使用 HTTPS + 证书。http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/
回答by Varun Chatterji
If you want to secure your API in a server to server fashion (no redirection to website for 2 legged authentication). You can look at OAuth2 Client Credentials Grant protocol.
如果您想以服务器到服务器的方式保护您的 API(不重定向到网站以进行 2 条腿身份验证)。您可以查看 OAuth2 客户端凭据授予协议。
https://dev.twitter.com/docs/auth/application-only-auth
https://dev.twitter.com/docs/auth/application-only-auth
I have developed a library that can help you easily add this kind of support to your WebAPI. You can install it as a NuGet package:
我开发了一个库,可以帮助您轻松地将这种支持添加到您的 WebAPI。您可以将其安装为 NuGet 包:
https://nuget.org/packages/OAuth2ClientCredentialsGrant/1.0.0.0
https://nuget.org/packages/OAuth2ClientCredentialsGrant/1.0.0.0
The library targets .NET Framework 4.5.
该库面向 .NET Framework 4.5。
Once you add the package to your project, it will create a readme file in the root of your project. You can look at that readme file to see how to configure/use this package.
将包添加到项目后,它将在项目的根目录中创建一个自述文件。您可以查看该自述文件以了解如何配置/使用此包。
Cheers!
干杯!
回答by Dalorzo
Web API introduced an Attribute [Authorize]to provide security. This can be set globally (global.asx)
Web API 引入了一个属性[Authorize]来提供安全性。这可以全局设置(global.asx)
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new AuthorizeAttribute());
}
Or per controller:
或每个控制器:
[Authorize]
public class ValuesController : ApiController{
...
Of course your type of authentication may vary and you may want to perform your own authentication, when this occurs you may find useful inheriting from Authorizate Attribute and extending it to meet your requirements:
当然,您的身份验证类型可能会有所不同,您可能希望执行自己的身份验证,当发生这种情况时,您可能会发现从授权属性继承并扩展它以满足您的要求很有用:
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;
}
}
}
And in your controller:
在您的控制器中:
[DemoAuthorize]
public class ValuesController : ApiController{
Here is a link on other custom implemenation for WebApi Authorizations:
这是 WebApi 授权的其他自定义实现的链接:
http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/
http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/
回答by refactor
in continuation to @ Cuong Le's answer , my approach to prevent replay attack would be
继续@ Cuong Le的回答,我防止重放攻击的方法是
// Encrypt the Unix Time at Client side using the shared private key(or user's password)
// 在客户端使用共享私钥(或用户密码)加密 Unix 时间
// Send it as part of request header to server(WEB API)
// 将它作为请求头的一部分发送到服务器(WEB API)
// Decrypt the Unix Time at Server(WEB API) using the shared private key(or user's password)
// 使用共享私钥(或用户密码)在服务器(WEB API)解密Unix时间
// Check the time difference between the Client's Unix Time and Server's Unix Time, should not be greater than x sec
// 检查Client的Unix Time和Server的Unix Time的时差,不应大于x秒
// if User ID/Hash Password are correct and the decrypted UnixTime is within x sec of server time then it is a valid request
// 如果用户 ID/哈希密码正确并且解密后的 UnixTime 在服务器时间的 x 秒内,那么它是一个有效的请求

