如何保护ASP.NET Web API

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

我已经阅读了很多关于OAuth的文章 ,它似乎是标准,但是找到一个很好的样本,并附有说明它如何工作的文档(实际上它确实有用!)似乎非常困难(特别是对于OAuth的新手来说)。

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

我已经下载了大量样本:

  • DotNetOAuth - 从新手的角度来看,文档是无望的
  • Thinktecture - 无法建立
  • 我也看过博客提出一个简单的基于令牌的方案(像这样) - 这看起来像重新发明轮子,但它确实具有概念上相当简单的优点。

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

    大家在这个领域做什么?


    更新:

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

    用于Asp.Net Web Api的JWT认证


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

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

  • 时间戳:发送请求的时间(UTC或GMT)
  • HTTP动词:GET,POST,PUT,DELETE。
  • 发布数据和查询字符串,
  • 网址
  • 在引擎盖下,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 ),但它应该可以正常工作。

    链接地址: http://www.djcxy.com/p/20513.html

    上一篇: How to secure an ASP.NET Web API

    下一篇: Is there an equivalent to ASP.NET Web API in the Rails world?