web-dev-qa-db-ja.com

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

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を呼び出すにはどうすればよいですか?

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

ただし、賞金はキャンセルできないため、実際にAddJwtBearerの呼び出しを待っているasyncawaitHttpClientを使用する方法を示すことができるすべての人に賞金を授与します。または、asyncawaitAddJwtBearerと一緒に使用されない理由についてのドキュメントを表示します。

8
Vaccano

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

ただし、スレッドをブロックしたくない場合、awaitを使用してオプションを取得するように要求する場合は、Program.csasyncawaitを使用して、オプションを使用して静的クラスに保存し、起動時に使用します。

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
Kahbazi

最初のカップルリクエストがOnMessageReceivedをトリガーできないのは、使用しているasync voidデリゲートが原因ではありませんが、ロードされるパラメーターとアタッチされるイベントの順序です。

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

これを修正するには、イベントハンドラbeforeを最初の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
    });
0
weichch