JwtBearerEvents.OnMessageReceivedが最初の操作の呼び出しで呼び出されない


8

IDプロバイダー(IDP)としてWSO2を使用しています。「X-JWT-Assertion」というヘッダーにJWTを配置しています。

これをASP.NET Coreシステムにフィードするために、OnMessageReceivedイベントを追加しました。これによりtoken、ヘッダーで指定された値にを設定できます。

ここに私がそれをしなければならないコードがあります(重要な部分は非ブラケットコードの最後の3行です):

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddJwtBearer(async options =>
{
    options.TokenValidationParameters = 
         await wso2Actions.JwtOperations.GetTokenValidationParameters();

    options.Events = new JwtBearerEvents()
    {
        // WSO2 sends the JWT in a different field than what is expected.
        // This allows us to feed it in.
        OnMessageReceived = context =>
        {
            context.Token = context.HttpContext.Request.Headers["X-JWT-Assertion"];
            return Task.CompletedTask;
        }
    }
};

これはすべて、サービスが起動した後の最初の呼び出しを除いて、完全に機能します。明確にするために、最初の呼び出しを除くすべての呼び出しは、期待どおりに機能します。(トークンを挿入し、User必要に応じてオブジェクトを更新します。)

しかし、最初の呼び出しでOnMessageReceivedは、ヒットしません。そして、User私のコントローラーのオブジェクトはセットアップされていません。

HttpContext最初の呼び出しを確認したところ、「X-JWT-Assertion」ヘッダーがRequest.Headersリストに含まれています(JWTが含まれています)。しかし、どういうわけか、そのOnMessageReceivedイベントはそのために呼ばれていません。

OnMessageReceivedサービスのサービス操作の最初の呼び出しでどのように呼び出されますか?

重要な注意:問題がasync awaitにあることがわかりましたAddJwtBearer。(以下の私の回答を参照してください。)それがこの質問に本当に欲しかったものです。

恵みがcancledすることはできませんので、しかし、私はまだ使用する方法を示すことができる誰にも報奨金を授与されますAddJwtBearerasync await、それは実際の待っているところHttpClientコールを。またはasync await、がでの使用が想定されていない理由のドキュメントを表示しますAddJwtBearer


このイベントハンドラーをbolierplate WebAPIテンプレートに入れました-最初のリクエストからハンドラーを取得するようです。ミドルウェアの注文が何らかの影響を及ぼしている可能性はありますか?
ティムール

1
@timur-質問の最後に私の更新を参照してください。(それはasync await呼び出しパイプラインでうまく機能しなかったためでした。)
Vaccano

それはそのようだAddJwtBearer(および基礎となるAuthenticationBuilder.AddSchemeHelper、それだけでsericesにIConfigureOptionsを追加する-そこに非同期呼び出しを期待しないでください)。OnMessageReceived一方で、-が待たれています。したがって、そのOnMessageReceivedラムダを非同期にして、http呼び出しをOnMessageReceivedbodyに移動して、結果をそこにキャッシュすることができるかどうか疑問に思っています。
ティムール

回答:


6

更新:
ラムダはActionメソッドです。何も返しません。それで非同期でそれをしようとすることはそれが火と忘れることなくして不可能です。

また、このメソッドは最初の呼び出しで呼び出されます。したがって、答えは、このメソッドで必要なものを事前に呼び出してキャッシュすることです。(ただし、依存関係が挿入されたアイテムを使用してこの呼び出しを行うための、ハック以外の方法はわかりません。)次に、最初の呼び出し中に、このラムダが呼び出されます。その時点で、必要な値をキャッシュからプルする必要があります(したがって、最初の呼び出しの速度はそれほど低下しません)。


これは私が最終的に考え出したことです。

のラムダは、AddJwtBearerでは機能しませんasync await。への私の呼び出しawait wso2Actions.JwtOperations.GetTokenValidationParameters();は問題なく待機しますが、呼び出しパイプラインは終了を待たずに続行しAddJwtBearerます。

async await呼び出し順序は次のようになります:

  1. サービスが起動します(そして、すべてが満足するまでしばらく待ちます)。
  2. サービスが呼び出されます。
  3. AddJwtBearer と呼ばれます。
  4. await wso2Actions.JwtOperations.GetTokenValidationParameters(); と呼ばれます。
  5. GetTokenValidationParameters() を呼び出す HttpClientwithをawait
  6. HttpClient発行者の公開署名鍵を取得するための待望の呼び出しを行います。
  7. ながら HttpClientが待機している元のコールの残りは通過します。イベントはまだ設定されていなかったため、通常どおりコールパイプラインを続行します。
    • これは、OnMessageReceivedイベントを「スキップしたように見える」場所です。
  8. HttpClient公開鍵を用いてレスポンスを取得します。
  9. 実行 AddJwtBearer継続。
  10. OnMessageReceivedイベントが設定されています。
  11. 2番目の呼び出しがサービスに対して行われます
  12. イベントは最終的にセットアップされたため、イベントが呼び出されます。(AddJwtBearer最初の呼び出しでのみ呼び出されます。)

したがって、待機が発生すると(この場合は最終的にHttpClient呼び出しにヒットして発行者署名キーを取得します)、最初の呼び出しの残りの部分が通過します。イベントのセットアップはまだ行われていないため、ハンドラーを呼び出すことはわかりません。

のラムダをAddJwtBearer非同期ではないように変更したところ、問題なく動作しました。

注:
ここでは2つの点が奇妙に見えます。

  1. AddJwtBearerサービスの最初の呼び出し時ではなく、起動時に呼び出されると思っていました。
  2. それがawaitを正しく適用できない場合AddJwtBearerasyncラムダ署名をサポートしないと私は思ったでしょう。

これがバグかどうかはわかりませんが、念のため1つとして投稿しました:https : //github.com/dotnet/aspnetcore/issues/20799


そこで競合状態を作成しました。問題を回避するには、ステップ4の前にステップ10を実行します。私の答えを見てください:)
weichch

@weichch-残念ながら、JWTをデコードするには、待機中の呼び出しが必要です。私があなたが示す方法を注文した場合、最初の呼び出しはトークン検証に失敗します。
ヴァッカーノ

すみません、気づかなかった別の競合状態がありました:)更新してみてください。元の答えは、パラメータの読み込みについて同じタスクを待っていましたOnMessageReceived
weichch

0

を使用GetAwaiter().GetResult()して、起動時に非同期コードを実行できます。スレッドをブロックしますが、1回しか実行されず、アプリケーションの起動時なので、問題ありません。

ただし、スレッドをブロックしたくない場合はawait、オプションを取得するために使用するように主張する場合は、async awaitin Program.csを使用してオプションを取得し、それを静的クラスに格納して、起動時に使用できます。

public class Program
{
    public static async Task Main(string[] args)
    {
        JwtParameter.TokenValidationParameters = await wso2Actions.JwtOperations.GetTokenValidationParameters();
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

public static class JwtParameter
{
    public static TokenValidationParameters TokenValidationParameters { get; set; }
}

0

最初のカップル要求をトリガーできないのは、使用してOnMessageReceivedいるasync voidデリゲートが原因ではなく、パラメーターのロード方法とイベントのアタッチ方法の順序です。

後の イベントハンドラーをアタッチしますawait。つまり、ここで競合状態を作成しました。たとえば、リクエストがawaitが完了する前に到着した場合、イベントハンドラーはアタッチされていません。OnMessageReceivedれてい。

これを修正するは、最初のの前にイベントハンドラをアタッチする必要がありますawait。これにより、常にイベントハンドラーがアタッチされることが保証されます。OnMessageReceived

このコードを試してください:

services.AddAuthentication(opt =>
    {
        // ...
    })
    .AddJwtBearer(async opt =>
    {
        var tcs = new TaskCompletionSource<object>();

        // Any code before the first await in this delegate can run
        // synchronously, so if you have events to attach for all requests
        // attach handlers before await.
        opt.Events = new JwtBearerEvents
        {
            // This method is first event in authentication pipeline
            // we have chance to wait until TokenValidationParameters
            // is loaded.
            OnMessageReceived = async context =>
            {
                // Wait until token validation parameters loaded.
                await tcs.Task;
            }
        };

        // This delegate returns if GetTokenValidationParametersAsync
        // does not complete synchronously 
        try
        {
            opt.TokenValidationParameters = await GetTokenValidationParametersAsync();
        }
        finally
        {
            tcs.TrySetResult(true);
        }

        // Any code here will be executed as continuation of
        // GetTokenValidationParametersAsync and may not 
        // be seen by first couple requests
    });
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.