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 OnMessageReceived
evento. Esto me permite establecer token
el 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 User
objeto como lo necesito).
Pero para la primera llamada, OnMessageReceived
no se golpea. Y el User
objeto en mi controlador no está configurado.
Verifiqué HttpContext
esa primera llamada y el encabezado "X-JWT-Assertion" está en la Request.Headers
lista (con el JWT en él). Pero, por alguna razón, el OnMessageReceived
evento no se requiere.
¿Cómo puedo OnMessageReceived
ser llamado para la primera invocación de una operación de servicio para mi servicio?
NOTA IMPORTANTE: descubrí que el problema estaba async
await
en 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 AddJwtBearer
con async
await
donde está a la espera de una verdadera HttpClient
llamada. O muestre la documentación de por qué async
await
no se debe utilizar AddJwtBearer
.
fuente
async
await
no 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 esaOnMessageReceived
lambda sea asíncrona, mover su llamada http alOnMessageReceived
cuerpo y de alguna manera almacenar los resultados de la caché allí.Respuestas:
ACTUALIZACIÓN:
La lambda es un
Action
mé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
AddJwtBearer
no funcionaasync
await
. Mi llamada aawait wso2Actions.JwtOperations.GetTokenValidationParameters();
espera está bien, pero la línea de llamadas continúa sin esperar aAddJwtBearer
que termine.Con
async
await
la orden de llamada va así:AddJwtBearer
se llama.await wso2Actions.JwtOperations.GetTokenValidationParameters();
se llama.GetTokenValidationParameters()
invoca unHttpClient
conawait
.HttpClient
hace una llamada esperada para obtener la clave de firma pública del emisor.HttpClient
espera, 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.OnMessageReceived
evento.HttpClient
obtiene la respuesta con la clave pública.AddJwtBearer
continúa.OnMessageReceived
evento está configurado.AddJwtBearer
solo 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
AddJwtBearer
no ser asíncrono y funcionó bien.Notas:
Dos cosas parecen extrañas aquí:
AddJwtBearer
se llamaría al inicio, no en la primera llamada del servicio.AddJwtBearer
no admitiría unaasync
firma 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
await
para obtener las opciones, se puede utilizarasync
await
enProgram.cs
conseguir 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
OnMessageReceived
no se debe alasync void
delegado 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 queawait
se 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