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
であることがわかりました。 (以下の私の回答を参照してください。)それがこの質問に本当に求めていたものです。
ただし、賞金はキャンセルできないため、実際にAddJwtBearer
の呼び出しを待っているasync
await
でHttpClient
を使用する方法を示すことができるすべての人に賞金を授与します。または、async
await
がAddJwtBearer
と一緒に使用されない理由についてのドキュメントを表示します。
GetAwaiter().GetResult()
を使用して、起動時に非同期コードを実行できます。スレッドをブロックしますが、1回しか実行されず、アプリケーションの起動中なので、問題ありません。
ただし、スレッドをブロックしたくない場合、await
を使用してオプションを取得するように要求する場合は、Program.cs
でasync
await
を使用して、オプションを使用して静的クラスに保存し、起動時に使用します。
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; }
}
最初のカップルリクエストが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
});