ASP.NET Web APIを保護する方法[終了]


397

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

OAuthについてはかなり読みましたが、OAuthは標準のようですが、OAuthがどのように機能するか(そして実際に機能するか!)

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

多数のサンプルをダウンロードしました:

  • DotNetOAuth-初心者の観点からはドキュメントは絶望的です
  • Thinktecture-構築できない

私はまた、(のような単純なトークンベースのスキームを示唆しているブログを見てきたこれを) -これは、車輪を再発明するように思えるが、それは概念的にはかなり簡単であるという利点を持っています。

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

この空間で皆は何をしているのですか?

回答:


292

更新:

ASP.NET Web APIJWT認証を使用する方法を、JWTに関心のあるユーザー向けにここに追加しました。


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

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

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

内部的には、HMAC認証は次のようになります。

コンシューマーは、HTTPリクエストのテンプレートである署名(hmacハッシュの出力)を構築した後、HTTPリクエストをWebサーバーに送信します。

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


2
@ジェームズ:タイムスタンプだけでは十分ではないようです。短期間でリクエストをシミュレートしてサーバーに送信します。投稿を編集したばかりです。両方を使用するのが最善です。
cuongle 2012

1
これは正常に機能していますか?メッセージでタイムスタンプをハッシュし、そのメッセージをキャッシュしています。これは、リクエストごとに異なる署名を意味するため、キャッシュされた署名が役に立たなくなります。
Filip Stas 2013

1
@FilipStas:私はあなたの要点を理解していないようです、ここでキャッシュを使用する理由はリレー攻撃を防ぐためです、それ以上何もありません
cuongle

1
@ChrisO:[このページ](jokecamp.wordpress.com/2012/10/21/…)を参照できます。私はすぐにこのソースを更新します
cuongle 2013年

1
提案された解決策は機能しますが、中間者攻撃を防ぐことはできません。そのため、HTTPSを実装する必要があります
リファクタリング

34

最初に最も簡単なソリューションから開始することをお勧めします。おそらく、シナリオでは単純な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/


23

DevDefined.OAuthを試しましたか?

私はこれを使用して、2-Legged OAuthでWebApiを保護しました。また、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、プロバイダーで使用しているコードを除く)をテストしていませんが、 正常に動作するはずです。


ありがとうございます。これを見ていきますが、今のところは、独自のHMACベースのソリューションを導入しました。
クレイグ・シアラー

1
@CraigShearer-こんにちは、あなたはあなたがあなた自身のものを転がしたと言います...あなたが共有を気にしないなら、いくつかの質問をしました。私も似たような立場にあり、比較的小さなMVC Web APIを持っています。APIコントローラーは、フォーム認証の下にある他のコントローラー/アクションと一緒に配置されます。OAuthの実装は、使用できるメンバーシッププロバイダーが既にあり、ほんの一握りの操作を保護する必要があるだけの場合、やり過ぎに思われます。暗号化されたトークンを返す認証アクションが本当に必要です-その後の呼び出しでトークンを使用しますか?既存の認証ソリューションの実装に取り​​掛かる前に、どんな情報でも歓迎します。ありがとう!
サンボマルティン

@Maksymilian Majer-プロバイダーを実装した方法をより詳細に共有できる可能性はありますか?クライアントに応答を送信するときに問題が発生しました。
jlrolin 2014年

21

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 Authorizationの他のカスタム実装に関するリンクです。

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


@Dalorzoの例に感謝しますが、いくつか問題があります。添付のリンクを見ましたが、その指示に従ってもうまくいきません。また、必要な情報が不足していることもわかりました。まず、新しいプロジェクトを作成するとき、認証に個別のユーザーアカウントを選択するのは適切ですか?または、認証なしのままにしますか?上記の302エラーは表示されませんが、401エラーが表示されます。最後に、ビューからコントローラに必要な情報を渡すにはどうすればよいですか?私のajax呼び出しはどのように見える必要がありますか?ところで、MVCビューにフォーム認証を使用しています。問題ありますか?
アマンダ

それは素晴らしく機能しています。自分のアクセストークンについて学び、作業を開始できるのは素晴らしいことです。
CodeName47 2015

1つの小さなコメント- AuthorizeAttribute同じ名前の2つの異なるクラスがあり、名前空間が異なるため、注意してください。1. System.Web.Mvc.AuthorizeAttribute-> MVCコントローラーの場合2. System.Web.Http.AuthorizeAttribute-> WebApiの場合。
Vitaliy Markitanov 2018

5

サーバー間でAPIを保護する場合(2レッグ認証でWebサイトにリダイレクトしないでください)。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を対象としています。

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

乾杯!


5
このフレームワークのソースコードをオープンソースとして共有/提供していますか?
バリーピッカー2014年

JFR:最初のリンクが壊れて、NuGetパッケージが更新されなかった
abdul qayyum

3

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

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

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

//共有秘密鍵(またはユーザーのパスワード)を使用して、Unix Time at Server(WEB API)を復号化します

//クライアントのUnix時間とサーバーのUnix時間の時間差を確認します。x秒以下にする必要があります

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.