首页 文章

如何保护ASP.NET Web API [关闭]

提问于
浏览
360

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

我已经阅读了很多关于 OAuth 的内容,它似乎是标准的,但找到一个很好的样本,文档解释它是如何工作的(实际上确实有效!)似乎非常困难(特别是对于OAuth的新手) .

是否有实际构建和工作的示例,并说明如何实现它?

我已经下载了很多样本:

  • DotNetOAuth - 从新手的角度来看,文档是没有希望的

  • Thinktecture - 无法构建它

我还看过一些博客,建议一个简单的基于令牌的方案(如this) - 这似乎是重新发明轮子但它确实具有概念上相当简单的优势 .

似乎在SO上有很多这样的问题,但没有好的答案 .

每个人在这个领域做什么?

6 回答

  • 271

    如果您希望以服务器到服务器的方式保护您的API(没有重定向到网站进行2腿认证) . 您可以查看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 .

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

    干杯!

  • 18

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

    //使用共享私钥(或用户密码)加密客户端的Unix时间

    //将其作为请求标头的一部分发送到服务器(WEB API)

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

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

    //如果用户ID /哈希密码正确且解密的UnixTime在服务器时间的x秒内,则它是有效请求

  • 2

    我建议首先从最简单的解决方案开始 - 也许简单的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/

  • 21

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

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

    或者每个控制器:

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

    当然,您的身份验证类型可能会有所不同,您可能希望执行自己的身份验证,当发生这种情况时,您可能会发现从Authorizate Attribute继承并扩展它以满足您的要求:

    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/

  • 29

    你试过DevDefined.OAuth吗?

    我用它来保护我的WebApi与2-legged 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 )但它应该工作正常 .

  • 5

    Update:

    对于对JWT感兴趣的人,我已经在这里提出了另一个答案,如何对Web API使用JWT身份验证:

    JWT Authentication for Asp.Net Web Api


    我们设法将HMAC身份验证应用于安全Web API,并且它运行正常 . HMAC身份验证为每个使用者使用一个密钥,消费者和服务器都知道hmac哈希消息,HMAC256应该是用过的 . 大多数情况下,消费者的散列密码被用作密钥 .

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

    • 时间戳:发送请求的时间(UTC或GMT)

    • HTTP动词:GET,POST,PUT,DELETE .

    • 发布数据和查询字符串,

    • 网址

    在引擎盖下,HMAC身份验证将是:

    在构建签名(hmac哈希的输出)之后,消费者向Web服务器发送HTTP请求,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动词,时间戳,uri,表单数据和查询字符串,然后根据这些来构建签名(使用hmac哈希)和秘密密钥(散列密码)在服务器上 .

    使用请求上的用户名从数据库获取密钥 .

    然后,服务器代码将请求上的签名与构建的签名进行比较;如果相等,则传递身份验证,否则失败 .

    构建签名的代码:

    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

相关问题