Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo descreve como autenticar e autorizar utilizadores em aplicações ASP.NET Core com SignalR.
Autenticar usuários que se conectam a um SignalR hub
SignalR pode ser usado com a autenticação ASP.NET Core para associar um usuário a cada conexão. Em um hub, os dados de autenticação podem ser acedidos na propriedade HubConnectionContext.User. A autenticação permite que o hub chame métodos em todas as conexões associadas a um usuário. Para obter mais informações, consulte Gerenciar usuários e grupos no SignalR. Múltiplas ligações podem ser associadas a um único utilizador.
O código a seguir é um exemplo que usa SignalR e autenticação do ASP.NET Core.
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SignalRAuthenticationSample.Data;
using SignalRAuthenticationSample.Hubs;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapHub<ChatHub>("/chat");
app.Run();
Note
Se um token expirar durante o tempo de vida de uma conexão, por padrão, a conexão continuará a funcionar.
LongPolling e ServerSentEvents as conexões falham em solicitações subsequentes se não enviarem novos tokens de acesso. Para que as ligações sejam encerradas quando o token de autenticação expirar, defina a opção CloseOnAuthenticationExpiration .
Alterações de utilizador e função durante a vida útil da ligação
SignalR captura o utilizador autenticado quando uma ligação é estabelecida e armazena-a em cache durante toda a vida útil da ligação. O principal armazenado em cache é exposto aos métodos do hub através da propriedade Context.User (HubCallerContext.User) e é utilizado para autorizar invocações de métodos do hub.
SignalR não revalida automaticamente o utilizador durante a vida útil da ligação, independentemente do esquema de autenticação. Este comportamento aplica-se a todos os esquemas, incluindo a autenticação cookie e a autenticação por token bearer.
Alterações à identidade, funções ou reivindicações de um utilizador que ocorrem após o estabelecimento da ligação não se refletem numa ligação existente. Este comportamento aplica-se mesmo quando o transporte subjacente faz novos pedidos HTTP, como os transportes LongPolling e ServerSentEvents. Embora o middleware de autenticação ASP.NET Core reautentique cada um destes pedidos HTTP, SignalR continua a usar o princípio que estava armazenado em cache quando a ligação foi estabelecida.
Considere o seguinte cenário:
- Uma aplicação utiliza a autenticação cookie e o transporte
LongPolling. O mesmo comportamento aplica-se à autenticação por bearer token e ao transporteServerSentEvents. - Um utilizador iniciou sessão com a função
Editore tem uma ligação SignalR aberta. - A aplicação remove o
Editorpapel desse utilizador.
No próximo pedido long-poll, o middleware de autenticação pode autenticar o utilizador sem a função Editor. Por exemplo, isto pode acontecer se as funções forem carregadas a partir de um repositório de dados ou se o cookie for atualizado. No entanto, o hub continua a autorizar as invocações do método do hub do utilizador [Authorize(Roles = "Editor")] porque Context.User (HubCallerContext.User) ainda mantém o principal que estava em cache antes de a função ser removida. O utilizador pode continuar a chamar métodos do hub apenas com Editor até que a ligação seja encerrada.
Para impor uma autorização atualizada numa ligação ativa, adote uma das seguintes abordagens:
- Feche as ligações afetadas para que os clientes se reconectem e voltem a autenticar. Para a autenticação do token portador, a opção CloseOnAuthenticationExpiration fecha as ligações quando o token de autenticação expira.
- Efetue verificações de autorização em métodos do hub com base nos dados atuais, como as funções atuais do utilizador ou as declarações existentes num armazenamento de dados, em vez de depender apenas do principal em cache.
No .NET 11 e posteriores, um cliente pode atualizar as credenciais para uma ligação ativa sem necessidade de reconectar. Quando o cliente apresenta um token atualizado, o servidor autentica-o novamente e substitui o Context.User em cache no próprio local, pelo que as invocações posteriores dos métodos do hub são autorizadas com base nas funções e declarações atualizadas. O principal renovado tem de mapear para o mesmo SignalR utilizador, pelo que uma renovação atualiza as funções e declarações, mas não altera a identidade do utilizador nem o encaminhamento da ligação. Para mais informações, consulte Atualização de Autenticação.
Cookie autenticação
Em uma aplicação baseada em navegador, a autenticação cookie permite que as credenciais existentes do utilizador sejam transmitidas automaticamente para conexões SignalR. Quando o cliente navegador é utilizado, não é necessária qualquer configuração adicional. Se o utilizador tiver sessão iniciada numa aplicação, a ligação SignalR herda automaticamente essa autenticação.
Cookies são uma forma específica dos navegadores de enviar tokens de acesso, mas clientes que não sejam navegadores também os podem enviar. Quando o cliente .NET é utilizado, a propriedade Cookies pode ser configurada na chamada .WithUrl para fornecer um cookie. No entanto, usar a autenticação do .NET cliente requer que o aplicativo forneça uma API para trocar dados de autenticação por um cookie.
Importante
A partir do ASP.NET Core 10, os pontos de extremidade da API conhecidos não redirecionam mais para as páginas de login ao usar autenticação com cookie. Em vez disso, eles retornam códigos de status 401/403. Para obter detalhes, consulte Comportamento de autenticação de ponto de extremidade da API no ASP.NET Core.
Bearer Autenticação de token
O cliente pode fornecer um token de acesso em vez de usar um cookie. O servidor valida o token e o usa para identificar o usuário. Para transportes que fazem múltiplos pedidos HTTP (por exemplo, LongPolling e ServerSentEvents), a autenticação é executada em cada pedido, mas SignalR coloca em cache o "principal" resultante durante o tempo de vida da ligação. Tal como na cookie autenticação, SignalR não revalida automaticamente o utilizador durante a vida útil da ligação para verificar revogação do token ou alterações nos papéis ou reivindicações do utilizador. Para mais informações, consulte Alterações ao utilizador e à função durante o tempo de vida da ligação.
No cliente JavaScript, o token pode ser fornecido usando a opção accessTokenFactory .
// Connect, using the token we got.
this.connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/chat", { accessTokenFactory: () => this.loginToken })
.build();
No cliente .NET, há uma propriedade AccessTokenProvider semelhante que pode ser usada para configurar o token:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chathub", options =>
{
options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
})
.Build();
Note
A função de token de acesso é chamada antes de cada pedido HTTP feito por SignalR. Se o token precisar de ser renovado para manter a ligação ativa, faça a renovação a partir desta função e devolve o token atualizado. O token pode precisar de ser renovado para não expirar durante a ligação.
Em APIs da Web padrão, os tokens de portador são enviados em um cabeçalho HTTP. No entanto, SignalR não consegue definir esses cabeçalhos nos navegadores quando são utilizados determinados transportes. Quando se utilizam WebSockets e eventos enviados pelo servidor, o token é transmitido como um parâmetro da string de consulta.
Autenticação incorporada JWT
No servidor, a autenticação do token portador é configurada usando o middleware JSON web token (JWT): Bearer
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using SignalRAuthenticationSample.Data;
using SignalRAuthenticationSample.Hubs;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using SignalRAuthenticationSample;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddAuthentication(options =>
{
// Identity made Cookie authentication the default.
// However, we want JWT Bearer Auth to be the default.
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
// Configure the Authority to the expected value for
// the authentication provider. This ensures the token
// is appropriately validated.
options.Authority = "Authority URL"; // TODO: Update URL
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
// Sending the access token in the query string is required when using WebSockets or ServerSentEvents
// due to a limitation in Browser APIs. We restrict it to only calls to the
// SignalR hub in this code.
// See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
// for more information about security considerations when using
// the query string to transmit the access token.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
// Change to use Name as the user identifier for SignalR
// WARNING: This requires that the source of your JWT token
// ensures that the Name claim is unique!
// If the Name claim isn't unique, users could receive messages
// intended for a different user!
builder.Services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
// Change to use email as the user identifier for SignalR
// builder.Services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
// WARNING: use *either* the NameUserIdProvider *or* the
// EmailBasedUserIdProvider, but do not use both.
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");
app.Run();
Note
A cadeia de caracteres de consulta é usada em navegadores ao se conectar com WebSockets e Server-Sent Events devido a limitações da API do navegador. Quando usas HTTPS, a ligação TLS protege os valores das strings de consulta. No entanto, muitos servidores registram valores de cadeia de caracteres de consulta. Para obter mais informações, consulte Considerações de segurança no ASP.NET Core SignalR. SignalR utiliza cabeçalhos para transmitir tokens em ambientes que os suportam, como os clientes .NET e Java.
IdentityAutenticação do servidor JWT
Ao usar o Duende IdentityServer, adicione um PostConfigureOptions<TOptions> serviço ao projeto:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
{
public void PostConfigure(string name, JwtBearerOptions options)
{
var originalOnMessageReceived = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
await originalOnMessageReceived(context);
if (string.IsNullOrEmpty(context.Token))
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
path.StartsWithSegments("/hubs"))
{
context.Token = accessToken;
}
}
};
}
}
Registe o serviço depois de adicionar os serviços de autenticação (com o método AddAuthentication) e o processador de autenticação para o servidor Identity (com o método AddIdentityServerJwt):
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection.Extensions;
using SignalRAuthenticationSample.Hubs;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>,
ConfigureJwtBearerOptions>());
builder.Services.AddRazorPages();
var app = builder.Build();
// Code removed for brevity.
Atualização da autenticação
Uma SignalR conexão pode manter-se ativa para além do token de acesso que a estabeleceu. Quando o CloseOnAuthenticationExpiration está ativado, o servidor fecha a ligação após o token expirar, e o cliente tem de se reconectar para continuar. As mensagens enviadas durante a interrupção perdem-se e o encaminhamento de grupos e utilizadores é interrompido até que o cliente se volte a ligar.
A atualização de autenticação, disponível em .NET 11 e posteriores, permite ao cliente atualizar as credenciais para uma ligação ativa sem se reconectar. O servidor volta a autenticar o pedido de renovação através do fluxo normal de autorização do ponto final e substitui, no próprio local, o ClaimsPrincipal da ligação, desde que a identidade atualizada corresponda ao mesmo utilizador SignalR.
Ativar a atualização de autenticação no servidor
Ative a atualização de autenticação nas opções do MapHub hub definindo EnableAuthenticationRefresh para true. Ative-o juntamente com CloseOnAuthenticationExpiration para que uma conexão cujo token expire sem ser atualizado atempadamente seja encerrada, em vez de permanecer aberta com credenciais obsoletas:
app.MapHub<ChatHub>("/chat", options =>
{
options.CloseOnAuthenticationExpiration = true;
options.EnableAuthenticationRefresh = true;
});
Quando a atualização de autenticação está ativada e o ticket de autenticação expira, a resposta de negociação reporta a vida útil restante do token para que o cliente possa agendar atualizações.
Para inspecionar ou rejeitar uma atualização, defina o OnAuthenticationRefresh callback. Executa depois de o pedido de atualização ser autenticado, mas antes de o utilizador da ligação ser substituído. Volte false para rejeitar a atualização, caso em que o endpoint responde com um código de estado HTTP 403 e a ligação mantém o seu utilizador atual. O callback é uma verificação adicional por cima da verificação incorporada de que o principal atualizado corresponde ao mesmo SignalR utilizador. Pode rejeitar uma atualização, mas não pode aprovar uma que falhe a verificação incorporada:
app.MapHub<ChatHub>("/chat", options =>
{
options.CloseOnAuthenticationExpiration = true;
options.EnableAuthenticationRefresh = true;
options.OnAuthenticationRefresh = context =>
{
if (!context.NewUser.HasClaim("tenant", "contoso"))
{
return Task.FromResult(false);
}
return Task.FromResult(true);
};
});
O principal atualizado deve corresponder ao mesmo SignalR utilizador que a ligação. Se corresponder a um ID de utilizador diferente, a atualização é rejeitada: o endpoint responde com um código de estado HTTP 403, e a ligação mantém o utilizador atual e mantém-se ligada. Uma atualização nunca altera a ligação Context.UserIdentifier nem redireciona as mensagens enviadas com Clients.User, mesmo para uma atualização bem-sucedida. O identificador de encaminhamento é fixo quando a ligação começa. Para o alterar, volte a ligar o cliente.
Para limitar até onde uma atualização pode estender a expiração da autenticação de uma ligação, defina MaximumAuthenticationExpiration. A nova expiração está limitada, no máximo, a este período a contar do momento atual, mesmo quando o token indica uma vida útil mais longa. Este limite aplica-se sempre que a atualização de autenticação está ativada, incluindo quando o token não define uma expiração própria. Nesse caso, o limite dá à ligação uma expiração conhecida, de modo que a resposta negociada reporta uma vida útil do token e o cliente pode agendar atualizações automáticas. O valor deve ser maior que zero e não se aplica à Windows authentication, que nunca é rastreada ou atualizada.
Atualizar autenticação a partir do cliente .NET
O cliente .NET atualiza as credenciais usando o AccessTokenProvider configurado na conexão. Cada atualização exige AccessTokenProvider obter um token de acesso novo em vez de reutilizar o token armazenado em cache quando a ligação começou.
Para atualizar explicitamente, chame RefreshAuthenticationAsync, que devolve a nova vida útil do token reportada pelo servidor:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chat", options =>
{
options.AccessTokenProvider = GetAccessTokenAsync;
})
.Build();
await connection.StartAsync();
TimeSpan? newLifetime = await connection.RefreshAuthenticationAsync();
Para atualizar automaticamente antes do token expirar, ligue WithAuthenticationRefresh e configure AuthenticationRefreshOptions:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chat", options =>
{
options.AccessTokenProvider = GetAccessTokenAsync;
})
.WithAuthenticationRefresh(options =>
{
options.RefreshBeforeExpiration = TimeSpan.FromMinutes(2);
})
.Build();
AuthenticationRefreshOptions fornece as seguintes definições:
-
EnableAutoRefresh: Ativa a atualização automática antes do token expirar. O valor padrão étrue. O cliente só agenda uma atualização quando o servidor reporta a vida útil de um token. Se o servidor não indicar um tempo de vida, não é agendada qualquer atualização automática eRefreshAuthenticationAsynccontinua a poder ser chamada manualmente. -
RefreshBeforeExpiration: Com quanta antecedência em relação à expiração comunicada deve ser feita a atualização. O valor predefinido é de cinco minutos.
Para observar atualizações, trate os eventos AuthenticationRefreshed e AuthenticationRefreshFailed na ligação. Tanto as atualizações automáticas como as manuais disparam estes eventos:
connection.AuthenticationRefreshed += context =>
{
Console.WriteLine(
$"Authentication refreshed. New lifetime: {context.NewTokenLifetime}");
return Task.CompletedTask;
};
connection.AuthenticationRefreshFailed += context =>
{
Console.WriteLine(
$"Authentication refresh failed: {context.Exception}");
return Task.CompletedTask;
};
Atualizar autenticação a partir do cliente JavaScript
O cliente JavaScript atualiza as credenciais usando o accessTokenFactory configurado na ligação. Cada atualização invoca accessTokenFactory para obter um novo token de acesso.
Para atualizar explicitamente, chame refreshAuthentication, que resolve com a nova vida útil do token em segundos reportada pelo servidor:
const newLifetimeInSeconds = await connection.refreshAuthentication();
Para atualizar automaticamente antes do token expirar, chame withAuthenticationRefresh e, opcionalmente, configure IAuthenticationRefreshOptions. Tratar dos resultados de atualização com onAuthenticationRefreshed e onAuthenticationRefreshFailed:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chat", {
accessTokenFactory: () => getAccessToken()
})
.withAuthenticationRefresh({
refreshBeforeExpirationInMilliseconds: 120000
})
.build();
connection.onAuthenticationRefreshed(context => {
console.log(
`Authentication refreshed. New lifetime: ${context.newTokenLifetimeInSeconds}`);
});
connection.onAuthenticationRefreshFailed(context => {
console.log(`Authentication refresh failed: ${context.error}`);
});
IAuthenticationRefreshOptions fornece as seguintes definições:
-
enableAutoRefresh: Ativa a atualização automática antes do token expirar. O valor padrão étrue. Tal como no cliente .NET, a atualização automática só é agendada quando o servidor reporta a vida útil de um token. -
refreshBeforeExpirationInMilliseconds: Com quanta antecedência em relação à expiração indicada deve ser efetuada a atualização, em milissegundos. O valor predefinido é 300.000 (cinco minutos).
Reagir a uma atualização no hub
Override OnAuthenticationRefreshedAsync no hub para executar código após a aplicação do principal atualizado à ligação.
Context.User reflete o diretor renovado. Como descrito anteriormente, o encaminhamento do utilizador Context.UserIdentifier e SignalR não muda com uma atualização:
public class ChatHub : Hub
{
public override Task OnAuthenticationRefreshedAsync()
{
return Clients.Caller.SendAsync(
"AuthenticationRefreshed", Context.UserIdentifier);
}
}
Um método do hub que já está em execução mantém o Context.User com que foi iniciado. As invocações subsequentes refletem o Context.User atualizado. Para mais informações sobre como SignalR armazena em cache o utilizador autenticado, consulte alterações de utilizador e função durante a vida útil da ligação.
A atualização de autenticação requer uma ligação que tenha negociado a versão 1 do protocolo ou posterior. A atualização automática é agendada apenas quando o servidor indica o tempo de vida de um token, o que normalmente provém de um esquema de autenticação que define um prazo de validade, como os tokens bearer. O Windows authentication não reporta uma expiração nem é rastreado ou atualizado por esta funcionalidade.
Cookies Versus Tokens de Portador
Cookies são específicas de cada navegador. Enviá-los de outros tipos de clientes aumenta a complexidade em comparação com o envio de tokens ao portador. Cookie A autenticação não é recomendada, a menos que o aplicativo só precise autenticar usuários do cliente do navegador. Bearer A autenticação por token é a abordagem recomendada ao utilizar clientes que não o cliente do navegador.
Windows authentication
Se a autenticação do Windows estiver configurada no aplicativo, SignalR poderá usar essa identidade para proteger hubs. No entanto, para enviar mensagens para usuários individuais, adicione um provedor de ID de usuário personalizado. O sistema de autenticação do Windows não fornece a declaração "Name Identifier". SignalR usa a declaração para determinar o nome de usuário.
Adicione uma nova classe que implemente IUserIdProvider e recupere uma das reivindicações do utilizador para usar como identificador. Por exemplo, para usar a declaração "Name" (que é o nome de usuário do Windows no formulário [Domain]/[Username]), crie a seguinte classe:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
Em vez de ClaimTypes.Name, use qualquer valor do User, como o identificador Windows SID, e assim sucessivamente.
Note
O valor especificado deve ser único entre todos os utilizadores do sistema. Caso contrário, uma mensagem destinada a um utilizador pode acabar por chegar a outro utilizador.
Registe este componente no ficheiro Program.cs:
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.SignalR;
using SignalRAuthenticationSample;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
});
services.AddRazorPages();
services.AddSignalR();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
var app = builder.Build();
// Code removed for brevity.
No cliente .NET, a autenticação do Windows tem de ser ativada definindo a propriedade UseDefaultCredentials:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chathub", options =>
{
options.UseDefaultCredentials = true;
})
.Build();
A autenticação do Windows é suportada no Microsoft Edge, mas não em todos os navegadores. Por exemplo, no Chrome e no Safari, a tentativa de usar a autenticação do Windows e WebSockets falha. Quando a autenticação do Windows falha, o cliente tenta mudar para outros transportes, que poderão funcionar.
Usar declarações para personalizar o tratamento de identidade
Um aplicativo que autentica usuários pode derivar SignalR IDs de usuário de declarações de usuário. Para especificar como SignalR cria IDs de usuário, implemente IUserIdProvider e registre a implementação.
O código de exemplo demonstra como usar declarações para selecionar o endereço de e-mail do usuário como a propriedade de identificação.
Note
O valor especificado deve ser único entre todos os utilizadores do sistema. Caso contrário, uma mensagem destinada a um utilizador pode acabar por chegar a outro utilizador.
public class EmailBasedUserIdProvider : IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(ClaimTypes.Email)?.Value!;
}
}
O registro de conta adiciona uma declaração com tipo ClaimsTypes.Email ao banco de dados de identidade ASP.NET.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
.ToList();
if (ModelState.IsValid)
{
var user = CreateUser();
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user, Input.Password);
// Add the email claim and value for this user.
await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, Input.Email));
// Remaining code removed for brevity.
Registe este componente no ficheiro Program.cs:
builder.Services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
Autorizar os usuários a acessar hubs e métodos de hub
Por defeito, um utilizador não autenticado pode chamar todos os métodos num hub. Para exigir autenticação, aplique o AuthorizeAttribute atributo ao hub:
[Authorize]
public class ChatHub: Hub
{
}
Os argumentos do construtor e as propriedades do atributo podem ser usados para restringir o [Authorize] acesso apenas a usuários que correspondam a políticas de autorização específicas. Por exemplo, com a política de autorização personalizada chamada MyAuthorizationPolicy, apenas utilizadores que correspondam a essa política podem aceder ao hub usando o seguinte código:
[Authorize("MyAuthorizationPolicy")]
public class ChatPolicyHub : Hub
{
public override async Task OnConnectedAsync()
{
await Clients.All.SendAsync("ReceiveSystemMessage",
$"{Context.UserIdentifier} joined.");
await base.OnConnectedAsync();
}
// Code removed for brevity.
O [Authorize] atributo pode ser aplicado a métodos de hub individuais. Se o usuário atual não corresponder à política aplicada ao método, um erro será retornado ao chamador:
[Authorize]
public class ChatHub : Hub
{
public async Task Send(string message)
{
// ... Send a message to all users ...
}
[Authorize("Administrators")]
public void BanUser(string userName)
{
// ... Ban a user from the chat room (something only Administrators can do) ...
}
}
Usar manipuladores de autorização para personalizar a autorização do método de hub
SignalR Fornece um recurso personalizado para manipuladores de autorização quando um método de hub requer autorização. O recurso é uma instância do HubInvocationContext. O HubInvocationContext inclui o HubCallerContext, o nome do método hub que está sendo invocado e os argumentos para o método hub.
Considere o exemplo de uma sala de chat que permite a múltiplas organizações iniciar sessão através do Microsoft Entra ID. Qualquer pessoa com uma conta Microsoft pode iniciar sessão no chat, mas apenas os membros da organização proprietária devem poder banir utilizadores ou ver os históricos de chat dos utilizadores. Além disso, pode ser necessário restringir alguma funcionalidade a utilizadores específicos. Neste cenário, repare como o DomainRestrictedRequirement funciona como um IAuthorizationRequirement personalizado. Como o HubInvocationContext parâmetro de recurso é passado, a lógica interna pode inspecionar o contexto em que o hub está a ser chamado e tomar decisões sobre como permitir ao utilizador executar métodos individuais do hub:
[Authorize]
public class ChatHub : Hub
{
public void SendMessage(string message)
{
}
[Authorize("DomainRestricted")]
public void BanUser(string username)
{
}
[Authorize("DomainRestricted")]
public void ViewUserHistory(string username)
{
}
}
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
namespace SignalRAuthenticationSample;
public class DomainRestrictedRequirement :
AuthorizationHandler<DomainRestrictedRequirement, HubInvocationContext>,
IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
DomainRestrictedRequirement requirement,
HubInvocationContext resource)
{
if (context.User.Identity != null &&
!string.IsNullOrEmpty(context.User.Identity.Name) &&
IsUserAllowedToDoThis(resource.HubMethodName,
context.User.Identity.Name) &&
context.User.Identity.Name.EndsWith("@microsoft.com"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
private bool IsUserAllowedToDoThis(string hubMethodName,
string currentUsername)
{
return !(currentUsername.Equals("asdf42@microsoft.com") &&
hubMethodName.Equals("banUser", StringComparison.OrdinalIgnoreCase));
}
}
No Program.cs ficheiro, adicione a nova política, fornecendo o requisito personalizado DomainRestrictedRequirement como parâmetro para criar a DomainRestricted política:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using SignalRAuthenticationSample;
using SignalRAuthenticationSample.Data;
using SignalRAuthenticationSample.Hubs;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var services = builder.Services;
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthorization(options =>
{
options.AddPolicy("DomainRestricted", policy =>
{
policy.Requirements.Add(new DomainRestrictedRequirement());
});
});
services.AddRazorPages();
var app = builder.Build();
// Code removed for brevity.
No exemplo anterior, a DomainRestrictedRequirement classe é simultaneamente uma IAuthorizationRequirement e a sua própria AuthorizationHandler para este requisito. É aceitável dividir esses dois componentes em classes separadas para separar preocupações. A abordagem neste exemplo oferece a vantagem de não ter de injetar o AuthorizationHandler durante o arranque, porque o requisito e o processador são a mesma coisa.
Como alternativa a registar a política DomainRestricted e a referenciá-la com [Authorize("DomainRestricted")], pode aplicar diretamente um atributo IAuthorizationRequirementData a um hub ou a um método do hub.
SignalR combina os requisitos do atributo na política eficaz para a invocação do método. Para obter mais informações, consulte Políticas de autorização personalizadas com 'IAuthorizationRequirementData'.
Recursos adicionais
Ver ou baixar código de exemplo(como baixar)
Autenticar usuários que se conectam a um SignalR hub
SignalR pode ser usado com a autenticação ASP.NET Core para associar um usuário a cada conexão. Em um hub, os dados de autenticação podem ser acedidos na propriedade HubConnectionContext.User. A autenticação permite que o hub chame métodos em todas as conexões associadas a um usuário. Para obter mais informações, consulte Gerenciar usuários e grupos no SignalR. Várias conexões podem ser associadas a um único usuário.
Segue-se um exemplo de Startup.Configure que utiliza SignalR e a autenticação do ASP.NET Core:
public void Configure(IApplicationBuilder app)
{
...
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/chat");
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
}
Note
Se um token expirar durante o tempo de vida de uma conexão, a conexão continuará a funcionar.
LongPolling e ServerSentEvents as conexões falham em solicitações subsequentes se não enviarem novos tokens de acesso.
Cookie autenticação
Em um aplicativo baseado em navegador, cookie a autenticação permite que suas credenciais de usuário existentes fluam automaticamente para SignalR as conexões. Ao usar o cliente do navegador, nenhuma configuração adicional é necessária. Se o usuário estiver conectado ao seu aplicativo, a SignalR conexão herdará automaticamente essa autenticação.
Cookies são uma forma específica dos navegadores de enviar tokens de acesso, mas os clientes não baseados em navegador também os podem enviar. Ao utilizar o cliente .NET, a Cookies propriedade pode ser configurada na chamada .WithUrl para fornecer um cookie. No entanto, usar a autenticação do .NET cliente requer que o aplicativo forneça uma API para trocar dados de autenticação por um cookie.
Bearer Autenticação de token
O cliente pode fornecer um token de acesso em vez de usar um cookie. O servidor valida o token e o usa para identificar o usuário. Essa validação é feita somente quando a conexão é estabelecida. Durante a vida útil da conexão, o servidor não revalida automaticamente para verificar a revogação do token.
No cliente JavaScript, o token pode ser fornecido usando a opção accessTokenFactory .
// Connect, using the token we got.
this.connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/chat", { accessTokenFactory: () => this.loginToken })
.build();
No cliente .NET, há uma propriedade AccessTokenProvider semelhante que pode ser usada para configurar o token:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chathub", options =>
{
options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
})
.Build();
Note
A função do token de acesso que você fornece é chamada antes de cada solicitação HTTP realizada por SignalR. Se você precisar renovar o token para manter a conexão ativa (porque ela pode expirar durante a conexão), faça isso de dentro dessa função e retorne o token atualizado.
Em APIs da Web padrão, os tokens de portador são enviados em um cabeçalho HTTP. No entanto, SignalR não é possível definir esses cabeçalhos em navegadores ao usar alguns transportes. Ao usar WebSockets e Server-Sent Events, o token é transmitido como um parâmetro de cadeia de caracteres de consulta.
Autenticação incorporada JWT
No servidor, a autenticação por token portador é configurada usando o JWT middleware portador:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
// Identity made Cookie authentication the default.
// However, we want JWT Bearer Auth to be the default.
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// Configure the Authority to the expected value for your authentication provider
// This ensures the token is appropriately validated
options.Authority = /* TODO: Insert Authority URL here */;
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
// Sending the access token in the query string is required when using WebSockets or ServerSentEvents
// due to a limitation in Browser APIs. We restrict it to only calls to the
// SignalR hub in this code.
// See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
// for more information about security considerations when using
// the query string to transmit the access token.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
// Change to use Name as the user identifier for SignalR
// WARNING: This requires that the source of your JWT token
// ensures that the Name claim is unique!
// If the Name claim isn't unique, users could receive messages
// intended for a different user!
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
// Change to use email as the user identifier for SignalR
// services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
// WARNING: use *either* the NameUserIdProvider *or* the
// EmailBasedUserIdProvider, but do not use both.
}
Note
A cadeia de caracteres de consulta é usada em navegadores ao se conectar com WebSockets e Server-Sent Events devido a limitações da API do navegador. Ao usar HTTPS, os valores da cadeia de caracteres de consulta são protegidos pela conexão TLS. No entanto, muitos servidores registram valores de cadeia de caracteres de consulta. Para obter mais informações, consulte Considerações de segurança no ASP.NET Core SignalR. SignalR usa cabeçalhos para transmitir tokens em ambientes que os suportam (como os clientes .NET e Java).
IdentityAutenticação do servidor JWT
Ao usar o Identity Server, adicione um PostConfigureOptions<TOptions> serviço ao projeto:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
{
public void PostConfigure(string name, JwtBearerOptions options)
{
var originalOnMessageReceived = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
await originalOnMessageReceived(context);
if (string.IsNullOrEmpty(context.Token))
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
path.StartsWithSegments("/hubs"))
{
context.Token = accessToken;
}
}
};
}
}
Registre o serviço em Startup.ConfigureServices depois de adicionar serviços para autenticação (AddAuthentication) e o manipulador de autenticação para Identity Servidor (AddIdentityServerJwt):
services.AddAuthentication()
.AddIdentityServerJwt();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>,
ConfigureJwtBearerOptions>());
Cookies vs. fichas de portador
Cookies são específicos dos navegadores. Enviá-los de outros tipos de clientes aumenta a complexidade em comparação com o envio de tokens ao portador. Consequentemente, a autenticação não é recomendada, cookie a menos que o aplicativo só precise autenticar usuários do cliente do navegador. Bearer A autenticação por token é a abordagem recomendada ao utilizar clientes que não o cliente do navegador.
Windows authentication
Se a autenticação do Windows estiver configurada em seu aplicativo, SignalR poderá usar essa identidade para proteger hubs. No entanto, para enviar mensagens para usuários individuais, você precisa adicionar um provedor de ID de usuário personalizado. O sistema de autenticação do Windows não fornece a declaração "Name Identifier". SignalR usa a declaração para determinar o nome de usuário.
Adicione uma nova classe que implemente IUserIdProvider e recupere uma das declarações do usuário para usar como identificador. Por exemplo, para usar a declaração "Name" (que é o nome de usuário do Windows no formulário [Domain]\[Username]), crie a seguinte classe:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
Em vez de ClaimTypes.Name, você pode usar qualquer valor do User (como o identificador SID do Windows e assim por diante).
Note
O valor escolhido deve ser único entre todos os usuários do seu sistema. Caso contrário, uma mensagem destinada a um usuário pode acabar indo para um usuário diferente.
Registe este componente no seu Startup.ConfigureServices método.
public void ConfigureServices(IServiceCollection services)
{
// ... other services ...
services.AddSignalR();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
}
No Cliente .NET, a Autenticação do Windows deve ser habilitada definindo a UseDefaultCredentials propriedade:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chathub", options =>
{
options.UseDefaultCredentials = true;
})
.Build();
A autenticação do Windows é suportada no Internet Explorer e no Microsoft Edge, mas não em todos os navegadores. Por exemplo, no Chrome e no Safari, a tentativa de usar a autenticação do Windows e WebSockets falha. Quando a autenticação do Windows falha, o cliente tenta recorrer a outros transportes que possam funcionar.
Usar declarações para personalizar o tratamento de identidade
Um aplicativo que autentica usuários pode derivar SignalR IDs de usuário de declarações de usuário. Para especificar como SignalR cria IDs de usuário, implemente IUserIdProvider e registre a implementação.
O código de exemplo demonstra como você usaria declarações para selecionar o endereço de email do usuário como a propriedade de identificação.
Note
O valor escolhido deve ser único entre todos os usuários do seu sistema. Caso contrário, uma mensagem destinada a um usuário pode acabar indo para um usuário diferente.
public class EmailBasedUserIdProvider : IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
}
}
O registro de conta adiciona uma declaração com tipo ClaimsTypes.Email ao banco de dados de identidade ASP.NET.
// create a new user
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
// add the email claim and value for this user
await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, Input.Email));
Registe este componente no seu Startup.ConfigureServices ficheiro.
services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
Autorizar os usuários a acessar hubs e métodos de hub
Por padrão, todos os métodos em um hub podem ser chamados por um usuário não autenticado. Para exigir autenticação, aplique o AuthorizeAttribute atributo ao hub:
[Authorize]
public class ChatHub: Hub
{
}
Você pode usar os argumentos do construtor e as propriedades do atributo para restringir o [Authorize] acesso apenas a usuários que correspondam a políticas de autorização específicas. Por exemplo, se você tiver uma política de autorização personalizada chamada MyAuthorizationPolicy , poderá garantir que apenas os usuários correspondentes a essa política possam acessar o hub usando o seguinte código:
[Authorize("MyAuthorizationPolicy")]
public class ChatHub : Hub
{
}
Os métodos de hub individuais também podem ter o [Authorize] atributo aplicado. Se o usuário atual não corresponder à política aplicada ao método, um erro será retornado ao chamador:
[Authorize]
public class ChatHub : Hub
{
public async Task Send(string message)
{
// ... send a message to all users ...
}
[Authorize("Administrators")]
public void BanUser(string userName)
{
// ... ban a user from the chat room (something only Administrators can do) ...
}
}
Usar manipuladores de autorização para personalizar a autorização do método de hub
SignalR Fornece um recurso personalizado para manipuladores de autorização quando um método de hub requer autorização. O recurso é uma instância do HubInvocationContext. O HubInvocationContext inclui o HubCallerContext, o nome do método hub que está sendo invocado e os argumentos para o método hub.
Considere o exemplo de uma sala de chat que permite o início de sessão em várias organizações através do Microsoft Entra ID. Qualquer pessoa com uma conta Microsoft pode iniciar sessão no chat, mas apenas os membros da organização proprietária devem poder banir utilizadores ou ver os históricos de chat dos utilizadores. Além disso, podemos querer restringir certas funcionalidades de certos utilizadores. Usando os recursos atualizados no ASP.NET Core 3.0, isso é totalmente possível. Observe como o DomainRestrictedRequirement serve como um costume IAuthorizationRequirement. Agora que o parâmetro HubInvocationContext recurso está a ser passado, a lógica interna pode inspecionar o contexto no qual o Hub está a ser chamado e tomar decisões sobre permitir ou não que o utilizador execute métodos individuais do Hub.
[Authorize]
public class ChatHub : Hub
{
public void SendMessage(string message)
{
}
[Authorize("DomainRestricted")]
public void BanUser(string username)
{
}
[Authorize("DomainRestricted")]
public void ViewUserHistory(string username)
{
}
}
public class DomainRestrictedRequirement :
AuthorizationHandler<DomainRestrictedRequirement, HubInvocationContext>,
IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
DomainRestrictedRequirement requirement,
HubInvocationContext resource)
{
if (IsUserAllowedToDoThis(resource.HubMethodName, context.User.Identity.Name) &&
context.User.Identity.Name.EndsWith("@microsoft.com"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
private bool IsUserAllowedToDoThis(string hubMethodName,
string currentUsername)
{
return !(currentUsername.Equals("asdf42@microsoft.com") &&
hubMethodName.Equals("banUser", StringComparison.OrdinalIgnoreCase));
}
}
No Startup.ConfigureServices, adicione a nova política, fornecendo o requisito personalizado DomainRestrictedRequirement como parâmetro para criar a DomainRestricted política.
public void ConfigureServices(IServiceCollection services)
{
// ... other services ...
services
.AddAuthorization(options =>
{
options.AddPolicy("DomainRestricted", policy =>
{
policy.Requirements.Add(new DomainRestrictedRequirement());
});
});
}
No exemplo anterior, a DomainRestrictedRequirement classe é simultaneamente uma IAuthorizationRequirement e a sua própria AuthorizationHandler para este requisito. É aceitável dividir esses dois componentes em classes separadas para separar preocupações. Um benefício da abordagem do exemplo é que não há necessidade de injetar o AuthorizationHandler durante a inicialização, pois o requisito e o manipulador são a mesma coisa.