Partilhar via


Autenticação e autorização no ASP.NET Core 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. Várias conexões podem ser associadas a um único usuário.

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 ServerSentEvent as conexões falham em solicitações subsequentes se não enviarem novos tokens de acesso. Para que as conexões sejam fechadas quando o token de autenticação expirar, defina CloseOnAuthenticationExpiration.

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. Ao usar o cliente do navegador, nenhuma configuração extra é necessária. Se o usuário estiver conectado a um aplicativo, a SignalR conexão herdará automaticamente essa autenticação.

Os cookies são uma forma específica de navegadores para enviar tokens de acesso, mas clientes que não utilizam navegador podem enviá-los. 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.

Importante

A partir do ASP.NET Core 10, os pontos de extremidade de API conhecidos não redirecionam mais para páginas de login ao usar cookie a autenticação. 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.

Autenticação de token portador

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 de token de acesso fornecida é chamada antes de cada solicitação HTTP feita pelo SignalR. Se o token precisar ser renovado para manter a conexão ativa, faça-o de dentro dessa função e retorne o token atualizado. O token pode precisar ser renovado para não expirar durante a conexão.

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 JWT integrada

No servidor, a autenticação de token de portador é configurada usando o middleware 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. 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).

Identity Autenticação JWT do servidor

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 após adicionar serviços para autenticação (AddAuthentication) e o manipulador de autenticação para o servidor (Identity) (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.

Cookies vs. tokens de portador

Os 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. Cookie A autenticação não é recomendada, a menos que o aplicativo só precise autenticar usuários do cliente do navegador. A autenticação de token de portador é a abordagem recomendada ao usar clientes diferentes do 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 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, use qualquer valor do User, como o identificador SID do Windows, etc.

Note

O valor escolhido deve ser único entre todos os usuários do sistema. Caso contrário, uma mensagem destinada a um usuário pode acabar indo para um usuário diferente.

Registe este componente em 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 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 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 usar declarações para selecionar o endereço de e-mail do usuário como a propriedade de identificação.

Note

O valor escolhido deve ser único entre todos os usuários do 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.

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 em Program.cs:

builder.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
{
}

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, somente os usuários que correspondem a essa política podem acessar o 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 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 algumas funcionalidades de usuários específicos. Observe como o DomainRestrictedRequirement serve como um costume IAuthorizationRequirement. Agora que o parâmetro de recurso HubInvocationContext 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 a permissão do utilizador para executar métodos do Hub individuais.

[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, 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. 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.

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 ServerSentEvent as conexões falham em solicitações subsequentes se não enviarem novos tokens de acesso.

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.

Os cookies são uma forma específica de navegadores para enviar tokens de acesso, mas clientes que não utilizam navegador podem enviá-los. 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.

Autenticação de token portador

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 JWT integrada

No servidor, a autenticação de token de portador é configurada usando o middleware JWT Bearer:

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. 
}

Se quiser ver os comentários de código traduzidos para outros idiomas além do inglês, avise-nos nesta discussão do GitHub .

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).

Identity Autenticação JWT do servidor

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. tokens de portador

Os 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. A autenticação de token de portador é a abordagem recomendada ao usar clientes diferentes do 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.

Recursos adicionais