Estoy usando WSO2 como mi proveedor de identidad (IDP). Está poniendo el JWT en un encabezado llamado "X-JWT-Assertion".
Para alimentar esto al sistema ASP.NET Core, agregué un OnMessageReceivedevento. Esto me permite establecer tokenel valor proporcionado en el encabezado.
Aquí está el código que tengo que hacer eso (la parte clave son las últimas 3 líneas de código sin paréntesis):
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;
}
}
};
Todo esto funciona perfectamente, excepto la primera llamada después de que se inicia el servicio. Para ser claros, cada llamada, excepto la primera, funciona exactamente como yo quiero. (Pone el token y actualiza el Userobjeto como lo necesito).
Pero para la primera llamada, OnMessageReceivedno se golpea. Y el Userobjeto en mi controlador no está configurado.
Verifiqué HttpContextesa primera llamada y el encabezado "X-JWT-Assertion" está en la Request.Headerslista (con el JWT en él). Pero, por alguna razón, el OnMessageReceivedevento no se requiere.
¿Cómo puedo OnMessageReceivedser llamado para la primera invocación de una operación de servicio para mi servicio?
NOTA IMPORTANTE: descubrí que el problema estaba async awaiten AddJwtBearer. (Vea mi respuesta a continuación.) Eso es lo que realmente quería de esta pregunta.
Sin embargo, desde una recompensa no puede ser cancled, yo todavía otorgará la recompensa a cualquiera que pueda demostrar una manera de utilizar AddJwtBearercon async awaitdonde está a la espera de una verdadera HttpClientllamada. O muestre la documentación de por qué async awaitno se debe utilizar AddJwtBearer.
fuente

asyncawaitno funcionaba bien con elAddJwtBearer(y subyacenteAuthenticationBuilder.AddSchemeHelper) no espera llamadas asíncronas allí, solo agrega IConfigureOptions a los servicios.OnMessageReceived, por otro lado, se está esperando. Entonces, me pregunto si podría hacer que esaOnMessageReceivedlambda sea asíncrona, mover su llamada http alOnMessageReceivedcuerpo y de alguna manera almacenar los resultados de la caché allí.Respuestas:
ACTUALIZACIÓN:
La lambda es un
Actionmétodo. No devuelve nada. Por lo tanto, tratar de hacer una asincronía no es posible sin que se dispare y se olvide.Además, este método se invoca en la primera llamada. Entonces, la respuesta es llamar a todo lo que necesita en este método con anticipación y almacenarlo en caché. (Sin embargo, no he descubierto una forma no pirateada de usar elementos inyectados de dependencia para hacer esta llamada). Luego, durante la primera llamada, se llamará a esta lambda. En ese momento, debe extraer los valores que necesita de la memoria caché (por lo tanto, no ralentizará mucho la primera llamada).
Esto es lo que finalmente descubrí.
La lambda para
AddJwtBearerno funcionaasyncawait. Mi llamada aawait wso2Actions.JwtOperations.GetTokenValidationParameters();espera está bien, pero la línea de llamadas continúa sin esperar aAddJwtBearerque termine.Con
asyncawaitla orden de llamada va así:AddJwtBearerse llama.await wso2Actions.JwtOperations.GetTokenValidationParameters();se llama.GetTokenValidationParameters()invoca unHttpClientconawait.HttpClienthace una llamada esperada para obtener la clave de firma pública del emisor.HttpClientespera, el resto de la llamada original continúa. Aún no se han configurado eventos, por lo que simplemente continúa con la canalización de llamadas de forma normal.OnMessageReceivedevento.HttpClientobtiene la respuesta con la clave pública.AddJwtBearercontinúa.OnMessageReceivedevento está configurado.AddJwtBearersolo se llama en la primera llamada).Entonces, cuando ocurre la espera (en este caso, finalmente llega a una llamada HttpClient para obtener la Clave de firma del emisor), el resto de la primera llamada pasa. Debido a que aún no se configuró el evento, no se sabe llamar al controlador.
Cambié el lambda de
AddJwtBearerno ser asíncrono y funcionó bien.Notas:
Dos cosas parecen extrañas aquí:
AddJwtBearerse llamaría al inicio, no en la primera llamada del servicio.AddJwtBearerno admitiría unaasyncfirma lambda si no pudiera aplicar correctamente la espera.No estoy seguro de si esto es un error o no, pero lo publiqué como uno solo por si acaso: https://github.com/dotnet/aspnetcore/issues/20799
fuente
OnMessageReceived.Puede usar
GetAwaiter().GetResult()para ejecutar código asíncrono en el inicio. Bloqueará el hilo, pero está bien porque solo se ejecuta una vez y está en el inicio de la aplicación.Sin embargo, si no te gusta para bloquear el hilo y insiste en usar
awaitpara obtener las opciones, se puede utilizarasyncawaitenProgram.csconseguir sus opciones y almacenarlo en una clase estática y utilizarlo en el inicio.fuente
La razón por la que sus primeras solicitudes de pareja no pueden activarse
OnMessageReceivedno se debe alasync voiddelegado que está utilizando, sino al orden de cómo se cargan los parámetros y los eventos que se adjuntan.Adjunta los controladores a los eventos posteriores
await, lo que significa que creó una condición de carrera aquí, que, por ejemplo, si llega una solicitud antes de queawaitse complete, no hay ningún controlador de eventos asociadoOnMessageReceived.Para solucionar esto, debe adjuntar controladores de eventos antes del primero
await. Esto garantizará que siempre tenga asociados controladores de eventosOnMessageReceived.Prueba este código:
fuente