Ověřování a autorizace v ASP.NET Core SignalR

Ověřování uživatelů připojujících se k SignalR centru

SignalRlze použít s ověřováním ASP.NET Core k přidružení uživatele ke každému připojení. V centru je možné k ověřovacím datům přistupovat z HubConnectionContext.User vlastnosti. Ověřování umožňuje centru volat metody pro všechna připojení přidružená k uživateli. Další informace naleznete v tématu Správa uživatelů a skupin v SignalR. K jednomu uživateli může být přidruženo více připojení.

Následující kód je příkladem, který používá SignalR a ASP.NET ověřování 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();

Poznámka

Pokud platnost tokenu vyprší během životnosti připojení, bude připojení ve výchozím nastavení fungovat. LongPolling a ServerSentEvent připojení selžou při následných požadavcích, pokud neodesílají nové přístupové tokeny. Pokud chcete připojení zavřít, když vyprší platnost ověřovacího tokenu, nastavte CloseOnAuthenticationExpiration.

V aplikaci cookie založené na prohlížeči ověřování umožňuje stávajícím přihlašovacím údajům uživatele automaticky tok připojení SignalR . Při použití klienta prohlížeče není potřeba žádná další konfigurace. Pokud je uživatel přihlášený k aplikaci, SignalR připojení toto ověřování automaticky zdědí.

Cookies představují způsob, jak odesílat přístupové tokeny konkrétním prohlížečem, ale klienti, kteří nejsou prohlížeči, je můžou odesílat. Při použití klienta .NET lze vlastnost nakonfigurovat ve .WithUrl volání tak, Cookies aby poskytovala cookie. Použití cookie ověřování z klienta .NET však vyžaduje, aby aplikace poskytovala rozhraní API pro výměnu ověřovacích dat pro .cookie

Ověřování nosný token

Klient může poskytnout přístupový token místo použití cookie. Server token ověří a použije ho k identifikaci uživatele. Toto ověření se provádí pouze při navázání připojení. Během životnosti připojení se server automaticky nerevaliduje, aby zkontroloval odvolání tokenu.

V javascriptovém klientovi je možné token poskytnout pomocí možnosti accessTokenFactory .

// Connect, using the token we got.
this.connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/chat", { accessTokenFactory: () => this.loginToken })
    .build();

V klientovi .NET je podobná vlastnost AccessTokenProvider , kterou lze použít ke konfiguraci tokenu:

var connection = new HubConnectionBuilder()
    .WithUrl("https://example.com/chathub", options =>
    { 
        options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
    })
    .Build();

Poznámka

Zadanou funkci přístupového tokenu se volá před každým požadavkem HTTP provedeným SignalR. Pokud je potřeba token obnovit, aby bylo připojení aktivní, proveďte to z této funkce a vraťte aktualizovaný token. Token může být potřeba obnovit, aby nevyprší platnost připojení.

Ve standardních webových rozhraních API se nosné tokeny odesílají v hlavičce HTTP. SignalR Při použití některých přenosů však není možné tyto hlavičky nastavit v prohlížečích. Pokud používáte WebSockets a Server-Sent Events, token se přenáší jako parametr řetězce dotazu.

Integrované ověřování JWT

Na serveru se ověřování nosných tokenů konfiguruje pomocí middlewaru 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();

Poznámka

Řetězec dotazu se používá v prohlížečích při připojování k webSocketům a událostem odesílaným serverem kvůli omezením rozhraní API prohlížeče. Při použití protokolu HTTPS jsou hodnoty řetězců dotazů zabezpečené připojením TLS. Mnoho serverů však protokoluje řetězcové hodnoty dotazu. Další informace najdete v tématu Aspekty zabezpečení v ASP.NET Core SignalR. SignalR používá hlavičky k přenosu tokenů v prostředích, která je podporují (jako jsou klienti .NET a Java).

Identity Ověřování JWT serveru

Při použití duende IdentityServeru přidejte PostConfigureOptions<TOptions> do projektu službu:

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

Zaregistrujte službu po přidání služeb pro ověřování (AddAuthentication) a obslužné rutiny ověřování pro Identity server (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. nosné tokeny

Cookiejsou specifické pro prohlížeče. Odesílání z jiných typů klientů zvyšuje složitost v porovnání s odesíláním nosných tokenů. Cookie ověřování se nedoporučuje, pokud aplikace potřebuje jenom ověřovat uživatele z klienta prohlížeče. Ověřování nosný token je doporučeným přístupem při použití jiných klientů než klienta prohlížeče.

Ověřování systému Windows

Pokud je v aplikaci nakonfigurované ověřování systému Windows, SignalR může tato identita použít k zabezpečení center. Pokud ale chcete odesílat zprávy jednotlivým uživatelům, přidejte vlastního zprostředkovatele ID uživatele. Ověřovací systém Windows neposkytuje deklaraci identity Identifikátor názvu. SignalR používá deklaraci identity k určení uživatelského jména.

Přidejte novou třídu, která implementuje IUserIdProvider a načte jednu z deklarací identity od uživatele, která se má použít jako identifikátor. Pokud například chcete použít deklaraci identity "Name" (což je uživatelské jméno systému Windows ve formuláři [Domain]/[Username]), vytvořte následující třídu:

public class NameUserIdProvider : IUserIdProvider
{
    public string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.Identity?.Name;
    }
}

ClaimTypes.NameMísto použití jakékoli hodnoty z identifikátoru UserSID systému Windows atd.

Poznámka

Zvolená hodnota musí být jedinečná mezi všemi uživateli v systému. Jinak může zpráva určená pro jednoho uživatele skončit jiným uživatelem.

Zaregistrovat tuto komponentu v 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.

V klientovi .NET musí být ověřování systému Windows povoleno nastavením UseDefaultCredentials vlastnosti:

var connection = new HubConnectionBuilder()
    .WithUrl("https://example.com/chathub", options =>
    {
        options.UseDefaultCredentials = true;
    })
    .Build();

Ověřování systému Windows je podporováno v Microsoft Edgi, ale ne ve všech prohlížečích. Například v Prohlížeči Chrome a Safari se pokus o použití ověřování systému Windows a webSocket nezdaří. Když ověřování systému Windows selže, klient se pokusí vrátit do jiných přenosů, které by mohly fungovat.

Přizpůsobení zpracování identit pomocí deklarací identity

Aplikace, která ověřuje uživatele, může odvodit SignalR ID uživatelů z deklarací identity uživatele. Pokud chcete určit, jak SignalR se vytvářejí ID uživatelů, implementujte IUserIdProvider a zaregistrujte implementaci.

Ukázkový kód ukazuje, jak pomocí deklarací identity vybrat e-mailovou adresu uživatele jako identifikační vlastnost.

Poznámka

Zvolená hodnota musí být jedinečná mezi všemi uživateli v systému. Jinak může zpráva určená pro jednoho uživatele skončit jiným uživatelem.

public class EmailBasedUserIdProvider : IUserIdProvider
{
    public virtual string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.FindFirst(ClaimTypes.Email)?.Value!;
    }
}

Registrace účtu přidá deklaraci identity s typem ClaimsTypes.Email do databáze ASP.NET identity.

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.

Zaregistrovat tuto komponentu v Program.cs:

builder.Services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();

Autorizace uživatelů pro přístup k centrem a metodám centra

Ve výchozím nastavení můžou všechny metody v centru volat neověřený uživatel. Pokud chcete vyžadovat ověření, použijte AuthorizeAttribute atribut v centru:

[Authorize]
public class ChatHub: Hub
{
}

Argumenty a vlastnosti konstruktoru atributu [Authorize] lze použít k omezení přístupu pouze uživatelům, kteří odpovídají konkrétním zásadám autorizace. Například s volanou MyAuthorizationPolicyvlastní autorizační zásadou , pouze uživatelé, kteří odpovídají této zásadě, mají přístup k centru pomocí následujícího kódu:

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

Atribut [Authorize] lze použít pro jednotlivé metody rozbočovače. Pokud aktuální uživatel neodpovídá zásadám použitým pro metodu, vrátí se volajícímu chyba:

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

Použití autorizačních obslužných rutin k přizpůsobení autorizace metod centra

SignalR poskytuje vlastní prostředek obslužným rutinům autorizace, pokud metoda centra vyžaduje autorizaci. Prostředek je instance HubInvocationContext. Zahrnuje HubInvocationContextHubCallerContext, název vyvolané metody centra a argumenty pro metodu centra.

Představte si příklad chatovací místnosti, která umožňuje přihlášení více organizací přes Microsoft Entra ID. Každý, kdo má účet Microsoft, se může přihlásit k chatu, ale jenom členové vlastnící organizace by měli mít možnost zakázat uživatele nebo zobrazit historie chatů uživatelů. Kromě toho můžeme chtít některé funkce omezit na konkrétní uživatele. Všimněte si, jak DomainRestrictedRequirement slouží jako vlastní IAuthorizationRequirement. Teď, když HubInvocationContext se předává parametr prostředku, může interní logika zkontrolovat kontext, ve kterém se centrum volá, a rozhodovat se o tom, jak uživateli umožnit spouštění jednotlivých metod centra:

[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));
    }
}

Přidejte Program.csnovou zásadu a zadejte vlastní DomainRestrictedRequirement požadavek jako parametr pro vytvoření DomainRestricted zásady:

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.

V předchozím příkladu DomainRestrictedRequirement je třída pro tento požadavek i IAuthorizationRequirement její vlastní AuthorizationHandler . Tyto dvě komponenty je přijatelné rozdělit do samostatných tříd a oddělit tak obavy. Výhodou tohoto příkladu je, že není nutné během spouštění vkládat, AuthorizationHandler protože požadavek a obslužná rutina jsou stejné.

Další prostředky

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Ověřování uživatelů připojujících se k SignalR centru

SignalRlze použít s ověřováním ASP.NET Core k přidružení uživatele ke každému připojení. V centru je možné k ověřovacím datům přistupovat z HubConnectionContext.User vlastnosti. Ověřování umožňuje centru volat metody pro všechna připojení přidružená k uživateli. Další informace naleznete v tématu Správa uživatelů a skupin v SignalR. K jednomu uživateli může být přidruženo více připojení.

Následuje příklad Startup.Configure použití SignalR a ASP.NET základního ověřování:

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?}");
    });
}

Poznámka

Pokud platnost tokenu vyprší během životnosti připojení, připojení bude fungovat i nadále. LongPolling a ServerSentEvent připojení selžou při následných požadavcích, pokud neodesílají nové přístupové tokeny.

V aplikaci cookie založené na prohlížeči ověřování umožňuje stávajícím uživatelským přihlašovacím údajům automaticky tok připojení SignalR . Při použití klienta prohlížeče není potřeba žádná další konfigurace. Pokud je uživatel přihlášený k vaší aplikaci, SignalR připojení toto ověřování automaticky zdědí.

Cookies představují způsob, jak odesílat přístupové tokeny konkrétním prohlížečem, ale klienti, kteří nejsou prohlížeči, je můžou odesílat. Při použití klienta .NET lze vlastnost nakonfigurovat ve .WithUrl volání tak, Cookies aby poskytovala cookie. Použití cookie ověřování z klienta .NET však vyžaduje, aby aplikace poskytovala rozhraní API pro výměnu ověřovacích dat pro .cookie

Ověřování nosný token

Klient může poskytnout přístupový token místo použití cookie. Server token ověří a použije ho k identifikaci uživatele. Toto ověření se provádí pouze při navázání připojení. Během životnosti připojení se server automaticky nerevaliduje, aby zkontroloval odvolání tokenu.

V javascriptovém klientovi je možné token poskytnout pomocí možnosti accessTokenFactory .

// Connect, using the token we got.
this.connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/chat", { accessTokenFactory: () => this.loginToken })
    .build();

V klientovi .NET je podobná vlastnost AccessTokenProvider , kterou lze použít ke konfiguraci tokenu:

var connection = new HubConnectionBuilder()
    .WithUrl("https://example.com/chathub", options =>
    { 
        options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
    })
    .Build();

Poznámka

Funkce přístupového tokenu, kterou zadáte, se volá před každým požadavkem HTTP provedeným SignalR. Pokud potřebujete token prodloužit, aby bylo připojení aktivní (protože může vypršet během připojení), proveďte to z této funkce a vraťte aktualizovaný token.

Ve standardních webových rozhraních API se nosné tokeny odesílají v hlavičce HTTP. SignalR Při použití některých přenosů však není možné tyto hlavičky nastavit v prohlížečích. Pokud používáte WebSockets a Server-Sent Events, token se přenáší jako parametr řetězce dotazu.

Integrované ověřování JWT

Na serveru se ověřování nosných tokenů konfiguruje pomocí middlewaru 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. 
}

Pokud chcete zobrazit komentáře ke kódu přeložené do jiných jazyků, než je angličtina, dejte nám vědět v této diskuzi na GitHubu.

Poznámka

Řetězec dotazu se používá v prohlížečích při připojování k webSocketům a událostem odesílaným serverem kvůli omezením rozhraní API prohlížeče. Při použití protokolu HTTPS jsou hodnoty řetězců dotazů zabezpečené připojením TLS. Mnoho serverů však protokoluje řetězcové hodnoty dotazu. Další informace najdete v tématu Aspekty zabezpečení v ASP.NET Core SignalR. SignalR používá hlavičky k přenosu tokenů v prostředích, která je podporují (jako jsou klienti .NET a Java).

Identity Ověřování JWT serveru

Při použití Identity serveru přidejte PostConfigureOptions<TOptions> do projektu službu:

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

Zaregistrujte službu Startup.ConfigureServices po přidání služeb pro ověřování (AddAuthentication) a obslužnou rutinu ověřování pro Identity server (AddIdentityServerJwt):

services.AddAuthentication()
    .AddIdentityServerJwt();
services.TryAddEnumerable(
    ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, 
        ConfigureJwtBearerOptions>());

Cookies vs. nosné tokeny

Cookiejsou specifické pro prohlížeče. Odesílání z jiných typů klientů zvyšuje složitost v porovnání s odesíláním nosných tokenů. Proto se ověřování nedoporučuje, cookie pokud aplikace nepotřebuje ověřovat uživatele jenom z klienta prohlížeče. Ověřování nosný token je doporučeným přístupem při použití jiných klientů než klienta prohlížeče.

Ověřování systému Windows

Pokud je ve vaší aplikaci nakonfigurované ověřování systému Windows, SignalR můžete tuto identitu použít k zabezpečení center. Pokud ale chcete odesílat zprávy jednotlivým uživatelům, musíte přidat vlastního zprostředkovatele ID uživatele. Ověřovací systém Windows neposkytuje deklaraci identity Identifikátor názvu. SignalR používá deklaraci identity k určení uživatelského jména.

Přidejte novou třídu, která implementuje IUserIdProvider a načte jednu z deklarací identity od uživatele, která se má použít jako identifikátor. Pokud například chcete použít deklaraci identity "Name" (což je uživatelské jméno systému Windows ve formuláři [Domain]\[Username]), vytvořte následující třídu:

public class NameUserIdProvider : IUserIdProvider
{
    public string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.Identity?.Name;
    }
}

ClaimTypes.NameMísto toho můžete použít libovolnou hodnotu z identifikátoru User SID systému Windows (například identifikátor SID systému Windows atd.).

Poznámka

Hodnota, kterou zvolíte, musí být jedinečná mezi všemi uživateli ve vašem systému. Jinak může zpráva určená pro jednoho uživatele skončit jiným uživatelem.

Zaregistrujte tuto komponentu ve své Startup.ConfigureServices metodě.

public void ConfigureServices(IServiceCollection services)
{
    // ... other services ...

    services.AddSignalR();
    services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
}

V klientovi .NET musí být ověřování systému Windows povoleno nastavením UseDefaultCredentials vlastnosti:

var connection = new HubConnectionBuilder()
    .WithUrl("https://example.com/chathub", options =>
    {
        options.UseDefaultCredentials = true;
    })
    .Build();

Ověřování systému Windows je podporováno v Internet Exploreru a Microsoft Edgi, ale ne ve všech prohlížečích. Například v Prohlížeči Chrome a Safari se pokus o použití ověřování systému Windows a webSocket nezdaří. Když ověřování systému Windows selže, klient se pokusí vrátit do jiných přenosů, které by mohly fungovat.

Přizpůsobení zpracování identit pomocí deklarací identity

Aplikace, která ověřuje uživatele, může odvodit SignalR ID uživatelů z deklarací identity uživatele. Pokud chcete určit, jak SignalR se vytvářejí ID uživatelů, implementujte IUserIdProvider a zaregistrujte implementaci.

Ukázkový kód ukazuje, jak pomocí deklarací identity vybrat e-mailovou adresu uživatele jako identifikační vlastnost.

Poznámka

Hodnota, kterou zvolíte, musí být jedinečná mezi všemi uživateli ve vašem systému. Jinak může zpráva určená pro jednoho uživatele skončit jiným uživatelem.

public class EmailBasedUserIdProvider : IUserIdProvider
{
    public virtual string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
    }
}

Registrace účtu přidá deklaraci identity s typem ClaimsTypes.Email do databáze ASP.NET identity.

// 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));

Zaregistrujte tuto komponentu ve svém Startup.ConfigureServicespočítači .

services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();

Autorizace uživatelů pro přístup k centrem a metodám centra

Ve výchozím nastavení můžou všechny metody v centru volat neověřený uživatel. Pokud chcete vyžadovat ověření, použijte AuthorizeAttribute atribut v centru:

[Authorize]
public class ChatHub: Hub
{
}

Pomocí argumentů a vlastností konstruktoru atributu [Authorize] můžete omezit přístup pouze na uživatele, kteří odpovídají konkrétním zásadám autorizace. Pokud máte například volanou MyAuthorizationPolicy vlastní zásadu autorizace, můžete pomocí následujícího kódu zajistit, aby k centru měli přístup jenom uživatelé odpovídající této zásadě:

[Authorize("MyAuthorizationPolicy")]
public class ChatHub : Hub
{
}

Jednotlivé metody rozbočovače mohou mít [Authorize] také použitý atribut. Pokud aktuální uživatel neodpovídá zásadám použitým pro metodu, vrátí se volajícímu chyba:

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

Použití autorizačních obslužných rutin k přizpůsobení autorizace metod centra

SignalR poskytuje vlastní prostředek obslužným rutinům autorizace, pokud metoda centra vyžaduje autorizaci. Prostředek je instance HubInvocationContext. Zahrnuje HubInvocationContextHubCallerContext, název vyvolané metody centra a argumenty pro metodu centra.

Představte si příklad chatovací místnosti, která umožňuje přihlášení více organizací přes Microsoft Entra ID. Každý, kdo má účet Microsoft, se může přihlásit k chatu, ale jenom členové vlastnící organizace by měli mít možnost zakázat uživatele nebo zobrazit historie chatů uživatelů. Kromě toho můžeme chtít omezit určité funkce od určitých uživatelů. Díky aktualizovaným funkcím v ASP.NET Core 3.0 je to zcela možné. Všimněte si, jak DomainRestrictedRequirement slouží jako vlastní IAuthorizationRequirement. Teď, když HubInvocationContext se předává parametr prostředku, může interní logika zkontrolovat kontext, ve kterém se centrum volá, a rozhodovat se o tom, jak uživateli umožnit spouštění jednotlivých metod centra.

[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));
    }
}

Přidejte Startup.ConfigureServicesnovou zásadu a zadejte vlastní DomainRestrictedRequirement požadavek jako parametr pro vytvoření DomainRestricted zásady.

public void ConfigureServices(IServiceCollection services)
{
    // ... other services ...

    services
        .AddAuthorization(options =>
        {
            options.AddPolicy("DomainRestricted", policy =>
            {
                policy.Requirements.Add(new DomainRestrictedRequirement());
            });
        });
}

V předchozím příkladu DomainRestrictedRequirement je třída pro tento požadavek i IAuthorizationRequirement její vlastní AuthorizationHandler . Tyto dvě komponenty je přijatelné rozdělit do samostatných tříd a oddělit tak obavy. Výhodou tohoto příkladu je, že není nutné během spouštění vkládat, AuthorizationHandler protože požadavek a obslužná rutina jsou stejné.

Další prostředky