Estou usando o WSO2 como meu provedor de identidade (IDP). Ele está colocando o JWT em um cabeçalho chamado "X-JWT-Assertion".
Para alimentar isso no sistema ASP.NET Core, adicionei um OnMessageReceived
evento. Isso permite que eu defina token
o valor fornecido no cabeçalho.
Aqui está o código que eu tenho que fazer isso (a parte principal são as últimas 3 linhas do código que não está entre colchetes):
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;
}
}
};
Tudo isso funciona perfeitamente, exceto na primeira chamada após o início do serviço. Para ser claro, todas as chamadas, exceto a primeira, funcionam exatamente como eu quero. (Ele coloca o token e atualiza o User
objeto como eu preciso.)
Mas para a primeira chamada, o OnMessageReceived
não é atingido. E o User
objeto no meu controlador não está configurado.
Eu verifiquei HttpContext
a primeira chamada e o cabeçalho "X-JWT-Assertion" está na Request.Headers
lista (com o JWT). Mas, por alguma razão, o OnMessageReceived
evento não é necessário.
Como posso OnMessageReceived
ser chamado para a primeira chamada de uma operação de serviço para o meu serviço?
NOTA IMPORTANTE: eu descobri que o problema era async
await
no AddJwtBearer
. (Veja minha resposta abaixo.) Era isso que eu realmente queria fora desta questão.
No entanto, como uma recompensa não pode ser excluída, ainda a concederei a qualquer pessoa que possa mostrar uma maneira de usá AddJwtBearer
- async
await
la onde está aguardando uma HttpClient
ligação real . Ou mostre a documentação do motivo pelo qual async
await
não deve ser usado AddJwtBearer
.
fonte
async
await
não funcionar bem com o pipeline de chamadas.)AddJwtBearer
(e subjacenteAuthenticationBuilder.AddSchemeHelper
) não esperam chamadas assíncronas por lá - apenas adiciona IConfigureOptions aos serviços.OnMessageReceived
, por outro lado - está sendo aguardado em. Então, eu estou querendo saber se você poderia fazer esseOnMessageReceived
lambda assíncrono, mover sua chamada http para oOnMessageReceived
corpo e, de alguma forma, armazenar os resultados lá?Respostas:
UPDATE:
O lambda é um
Action
método. Não retorna nada. Portanto, tentar fazer assincronia não é possível sem que seja fogo e esqueça.Além disso, esse método é chamado na primeira chamada. Portanto, a resposta é chamar qualquer coisa que você precise neste método com antecedência e armazená-lo em cache. (No entanto, eu não descobri uma maneira não hackeada de usar itens injetados em dependência para fazer essa chamada.) Então, durante a primeira chamada, esse lambda será chamado. Nesse momento, você deve extrair os valores necessários do cache (não diminuindo muito a velocidade da primeira chamada).
Isto é o que eu finalmente descobri.
O lambda para
AddJwtBearer
não funciona comasync
await
. Minha ligaçãoawait wso2Actions.JwtOperations.GetTokenValidationParameters();
aguarda muito bem, mas o pipeline de chamadas continua sem esperar paraAddJwtBearer
terminar.Com
async
await
a ordem de chamada, é assim:AddJwtBearer
é chamado.await wso2Actions.JwtOperations.GetTokenValidationParameters();
é chamado.GetTokenValidationParameters()
chama umHttpClient
comawait
.HttpClient
faz uma chamada aguardada para obter a chave de assinatura pública do emitente.HttpClient
aguarda, o restante da chamada original é finalizado. Nenhum evento foi configurado ainda, portanto, ele continua com o pipeline de chamadas normalmente.OnMessageReceived
evento.HttpClient
obtém a resposta com a chave pública.AddJwtBearer
continua.OnMessageReceived
evento está configurado.AddJwtBearer
é chamado apenas na primeira chamada.)Portanto, quando a espera acontece (nesse caso, eventualmente, uma ligação HttpClient para obter a chave de assinatura do emissor), o restante da primeira chamada é finalizada. Como ainda não havia configuração de evento, ele não sabe chamar o manipulador.
Mudei o lambda
AddJwtBearer
para não ser assíncrono e funcionou muito bem.Notas:
Duas coisas parecem estranhas aqui:
AddJwtBearer
seria chamado na inicialização, não na primeira chamada do serviço.AddJwtBearer
não suportaria umaasync
assinatura lambda se não pudesse aplicar corretamente o aguardar.Não tenho certeza se isso é um bug ou não, mas eu o publiquei como um caso: https://github.com/dotnet/aspnetcore/issues/20799
fonte
OnMessageReceived
.Você pode usar
GetAwaiter().GetResult()
para executar código assíncrono na inicialização. Ele bloqueará o encadeamento, mas tudo bem, porque ele é executado apenas uma vez e está na inicialização do aplicativo.No entanto, se você não gosta de bloquear a linha e insistir em usar
await
para obter as opções, você pode usarasync
await
emProgram.cs
obter as suas opções e armazená-lo em uma classe estática e usá-lo na inicialização.fonte
A razão pela qual suas primeiras solicitações não podem ser acionadas não
OnMessageReceived
se deve aoasync void
delegado que você está usando, mas à ordem de como os parâmetros estão sendo carregados e os eventos estão sendo anexados.Você anexa os manipuladores aos eventos depois
await
, o que significa que você criou uma condição de corrida aqui, que, se uma solicitação chegar antes daawait
conclusão, não há nenhum manipulador de eventos anexadoOnMessageReceived
.Para corrigir isso, você deve anexar manipuladores de eventos antes do primeiro
await
. Isso garantirá que você sempre tenha manipuladores de eventos conectadosOnMessageReceived
.Tente este código:
fonte