web-dev-qa-db-ja.com

ASP.NET Web APIをセキュリティで保護する方法

私は、ASP.NET Web APIを使用してサードパーティの開発者が私のアプリケーションのデータにアクセスするために使用する RESTful Webサービスを構築したいと思います。

OAuth についてはかなり読んできましたが、これは標準と思われますが、それがどのように動作するのか(そして実際に動作するのか)を説明するドキュメント付きの良いサンプルを見つけるのは非常に難しいようです。 OAuthの初心者).

実際に構築して動作し、これを実装する方法を示すサンプルはありますか?

私はたくさんのサンプルをダウンロードしました:

  • DotNetOAuth - ドキュメンテーションは初心者の観点からは絶望的
  • 思考 - それを構築することはできません

私はまた、単純なトークンベースのスキーム( this のような)を提案するブログを見ました - これはホイールを再発明するように思えますが、それは概念的にかなり単純であるという利点があります。

SOにはこのような質問がたくさんありますが、良い答えはありません。

この空間でみんなが何をしているの?

370
Craig Shearer

更新:

私はJWTに興味がある人のためにここにWeb APIのためにJWT認証を使用する方法を私の別の答えを入れました:

Asp.Net Web ApiのJWT認証


私たちは安全なWeb APIにHMAC認証を適用することに成功しました、そしてそれはうまくいきました。 HMAC認証では、コンシューマとサーバーの両方がメッセージをhmacハッシュすることを知っているコンシューマごとに秘密鍵を使用します。HMAC256を使用する必要があります。ほとんどの場合、消費者のハッシュされたパスワードは秘密鍵として使用されます。

メッセージは通常、HTTPリクエスト内のデータ、またはHTTPヘッダーに追加されるカスタマイズされたデータから構築されます。メッセージには次のものが含まれます。

  1. タイムスタンプ:要求が送信された時刻(UTCまたはGMT)
  2. HTTP動詞:GET、POST、PUT、DELETE。
  3. 投稿データとクエリ文字列
  4. URL

内部的には、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=

署名を得るためにハッシュするメッセージ:

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、フォームデータ、クエリ文字列。サーバー上のキー(ハッシュされたパスワード)。

秘密鍵は、要求時にユーザー名を使用してデータベースから取得されます。

次に、サーバーコードはリクエストの署名と作成された署名を比較します。等しい場合は認証に合格し、それ以外の場合は失敗します。

署名を作成するためのコード

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

277
cuongle

私はまず最も簡単な解決策から始めることをお勧めします - あなたのシナリオではおそらく単純な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/

30
Piotr Walat

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を除く)、それはうまく動くはずです。

22

Web APIはセキュリティを提供するために属性[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/ /

20
Dalorzo

サーバー間のやり方でAPIを保護したい場合(2本足認証のためにWebサイトにリダイレクトしない)。 OAuth2 Client Credentials Grantプロトコルをご覧ください。

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

この種のサポートをWebAPIに簡単に追加できるようにするためのライブラリを開発しました。 NuGetパッケージとしてインストールすることができます。

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

ライブラリは.NET Framework 4.5を対象としています。

パッケージをプロジェクトに追加すると、プロジェクトのルートにreadmeファイルが作成されます。このreadmeファイルを見て、このパッケージの設定方法や使用方法を確認することができます。

乾杯!

5
Varun Chatterji

@ Cuong Leの答えに続いて、リプレイ攻撃を防ぐための私のアプローチは

//共有秘密鍵(またはユーザーのパスワード)を使用してクライアント側でUnix時間を暗号化する

//リクエストヘッダの一部としてサーバーに送信する(WEB API)

//共有秘密鍵(またはユーザのパスワード)を使用して、サーバでのUnix時刻(WEB API)を復号化する

//クライアントのUnix時間とサーバーのUnix時間の時差をチェックします。x秒以下でなければなりません

//ユーザーID /ハッシュパスワードが正しく、復号化されたUnixTimeがサーバー時間のx秒以内であれば、それは有効な要求です

2
refactor