Dela via


Autentisering och auktorisering i ASP.NET Core SignalR

Autentisera användare som ansluter till en SignalR hubb

SignalR kan användas med ASP.NET Core-autentisering för att associera en användare med varje anslutning. I en hubb kan autentiseringsdata nås från egenskapen HubConnectionContext.User . Med autentisering kan hubben anropa metoder för alla anslutningar som är associerade med en användare. Mer information finns i Hantera användare och grupper i SignalR. Flera anslutningar kan associeras med en enskild användare.

Följande kod är ett exempel som använder SignalR och ASP.NET Core-autentisering:

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

Om en token upphör att gälla under en anslutning fortsätter anslutningen som standard att fungera. LongPolling och ServerSentEvent anslutningar misslyckas på efterföljande begäranden om de inte skickar nya åtkomsttoken. För att anslutningar ska stängas när autentiseringstoken upphör att gälla anger du CloseOnAuthenticationExpiration.

I en webbläsarbaserad app cookie tillåter autentisering att befintliga användarautentiseringsuppgifter automatiskt flödar till SignalR anslutningar. När du använder webbläsarklienten behövs ingen extra konfiguration. Om användaren är inloggad i en app ärver anslutningen automatiskt den här autentiseringen SignalR .

Cookies är ett webbläsarspecifikt sätt att skicka åtkomsttoken, men klienter som inte är webbläsare kan skicka dem. När du använder .NET-klienten, kan egenskapen Cookies konfigureras i .WithUrl-anropet för att ange en cookie. Om du använder cookie autentisering från .NET-klienten måste appen dock tillhandahålla ett API för att utbyta autentiseringsdata för en cookie.

Viktigt!

Från och med ASP.NET Core 10 omdirigeras inte längre kända API-slutpunkter till inloggningssidor när autentisering används cookie . I stället returnerar de statuskoder för 401/403. Mer information finns i BETEENDE för API-slutpunktsautentisering i ASP.NET Core.

Autentisering med ägartoken

Klienten kan tillhandahålla en åtkomsttoken i stället för att använda en cookie. Servern verifierar token och använder den för att identifiera användaren. Den här verifieringen görs endast när anslutningen upprättas. Under anslutningens gång revaliderar inte servern automatiskt för att kontrollera om en token har återkallats.

I JavaScript-klienten kan token anges med hjälp av alternativet accessTokenFactory .

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

I .NET-klienten finns det en liknande AccessTokenProvider-egenskap som kan användas för att konfigurera token:

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

Note

Den angivna funktionen för åtkomsttoken anropas före varje HTTP-begäran som görs av SignalR. Om token behöver förnyas för att hålla anslutningen aktiv gör du det inifrån den här funktionen och returnerar den uppdaterade token. Token kan behöva förnyas så att den inte upphör att gälla under anslutningen.

I standardwebb-API:er skickas bearertoken i en HTTP-header. Dock kan SignalR inte ange dessa huvuden i webbläsare när du använder vissa transporter. När du använder WebSockets och Server-Sent Events överförs token som en frågesträngsparameter.

Inbyggd JWT-autentisering

På servern konfigureras autentisering av ägartoken med hjälp av JWT Bearer-mellanprogrammet:

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

Frågesträngen används i webbläsare vid anslutning med WebSockets och Server-Sent Events på grund av begränsningar i API:erna för webbläsare. När du använder HTTPS skyddas frågesträngsvärdena av TLS-anslutningen. Många servrar loggar dock frågesträngsvärden. Mer information finns i Säkerhetsöverväganden i ASP.NET Core SignalR. SignalR använder rubriker för att överföra token i miljöer som stöder dem (till exempel .NET- och Java-klienter).

Identity JWT-autentisering för server

När du använder Duende IdentityServer lägger du till en PostConfigureOptions<TOptions> tjänst i projektet:

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

Registrera tjänsten när du har lagt till tjänster för autentisering (AddAuthentication) och autentiseringshanteraren för 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 jämfört med bäraretoken

Cookies är specifika för webbläsare. Att skicka dem från andra typer av klienter ökar komplexiteten jämfört med att skicka ägartoken. Cookie autentisering rekommenderas inte om inte appen bara behöver autentisera användare från webbläsarklienten. Autentisering med ägartoken är den rekommenderade metoden när du använder andra klienter än webbläsarklienten.

Windows authentication

Om Windows-autentisering har konfigurerats i appen SignalR kan du använda den identiteten för att skydda hubbar. Om du vill skicka meddelanden till enskilda användare lägger du dock till en anpassad användar-ID-provider. Windows-autentiseringssystemet tillhandahåller inte anspråk på "Namnidentifierare". SignalR använder anspråket för att fastställa användarnamnet.

Lägg till en ny klass som implementerar IUserIdProvider och hämtar ett av anspråken från användaren som ska användas som identifierare. Om du till exempel vill använda anspråket "Namn" (som är Windows-användarnamnet i formuläret [Domain]/[Username]) skapar du följande klass:

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

I stället ClaimTypes.Nameför använder du valfritt värde från User, till exempel Windows SID-identifierare osv.

Note

Det valda värdet måste vara unikt bland alla användare i systemet. Annars kan ett meddelande som är avsett för en användare hamna på en annan användare.

Registrera den här komponenten i 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.

I .NET-klienten måste Windows-autentisering aktiveras genom att egenskapen anges UseDefaultCredentials :

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

Windows-autentisering stöds i Microsoft Edge, men inte i alla webbläsare. I Chrome och Safari misslyckas till exempel försök att använda Windows-autentisering och WebSockets. När Windows-autentiseringen misslyckas försöker klienten återgå till andra transporter som kan fungera.

Använda anspråk för att anpassa identitetshantering

En app som autentiserar användare kan härleda SignalR användar-ID från användaranspråk. Om du vill ange hur SignalR skapar användar-ID:n, implementera IUserIdProvider och registrera implementeringen.

Exempelkoden visar hur du använder anspråk för att välja användarens e-postadress som identifierande egenskap.

Note

Det valda värdet måste vara unikt bland alla användare i systemet. Annars kan ett meddelande som är avsett för en användare hamna på en annan användare.

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

Kontoregistreringen lägger till ett anspråk med typen ClaimsTypes.Email i ASP.NET identitetsdatabasen.

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.

Registrera den här komponenten i Program.cs:

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

Auktorisera användare att komma åt hubbar och hubbmetoder

Som standard kan alla metoder i en hubb anropas av en oautentiserad användare. Använd attributet för hubben för att kräva autentisering AuthorizeAttribute :

[Authorize]
public class ChatHub: Hub
{
}

Konstruktorargumenten och egenskaperna för [Authorize] attributet kan användas för att begränsa åtkomsten till endast användare som matchar specifika auktoriseringsprinciper. Med den anpassade auktoriseringsprincipen kallas MyAuthorizationPolicytill exempel endast användare som matchar den principen kan komma åt hubben med hjälp av följande kod:

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

Attributet [Authorize] kan tillämpas på enskilda hubbmetoder. Om den aktuella användaren inte matchar principen som tillämpas på metoden returneras ett fel till anroparen:

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

Använda auktoriseringshanterare för att anpassa hubbmetodauktorisering

SignalR tillhandahåller en anpassad resurs till auktoriseringshanterare när en hubbmetod kräver auktorisering. Resursen är en instans av HubInvocationContext. HubInvocationContext innehåller HubCallerContext, namnet på hubbmetoden som anropas och argumenten till hubbmetoden.

Tänk dig ett exempel på ett chattrum som tillåter flera organisationsinloggning via Microsoft Entra-ID. Alla med ett Microsoft-konto kan logga in för att chatta, men endast medlemmar i den ägande organisationen bör kunna förbjuda användare eller visa användarnas chatthistorik. Dessutom kanske vi vill begränsa vissa funktioner från specifika användare. Observera hur DomainRestrictedRequirement fungerar som en anpassad IAuthorizationRequirement. Nu när HubInvocationContext resursparametern skickas in kan den interna logiken granska kontexten där hubben anropas och fatta beslut om att tillåta användaren att köra enskilda hubbmetoder:

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

I Program.cslägger du till den nya principen och anger det anpassade DomainRestrictedRequirement kravet som en parameter för att skapa DomainRestricted principen:

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.

I det föregående exemplet är DomainRestrictedRequirement-klassen både en IAuthorizationRequirement och sin egen AuthorizationHandler för det kravet. Det är acceptabelt att dela upp dessa två komponenter i separata klasser för att separera problem. En fördel med exemplets metod är att det inte finns något behov av att mata in AuthorizationHandler under starten, eftersom kravet och hanteraren är samma sak.

Ytterligare resurser

Visa eller ladda ned exempelkod(ladda ned)

Autentisera användare som ansluter till en SignalR hubb

SignalR kan användas med ASP.NET Core-autentisering för att associera en användare med varje anslutning. I en hubb kan autentiseringsdata nås från egenskapen HubConnectionContext.User . Med autentisering kan hubben anropa metoder för alla anslutningar som är associerade med en användare. Mer information finns i Hantera användare och grupper i SignalR. Flera anslutningar kan associeras med en enskild användare.

Följande är ett exempel på Startup.Configure vilka använder SignalR och ASP.NET Core-autentisering:

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

Om en token upphör att gälla under en anslutning fortsätter anslutningen att fungera. LongPolling och ServerSentEvent anslutningar misslyckas på efterföljande begäranden om de inte skickar nya åtkomsttoken.

I en webbläsarbaserad app cookie gör autentisering att dina befintliga användarautentiseringsuppgifter automatiskt flödar till SignalR anslutningar. När du använder webbläsarklienten behövs ingen ytterligare konfiguration. Om användaren är inloggad i din app, ärver SignalR-anslutningen automatiskt denna autentisering.

Cookies är ett webbläsarspecifikt sätt att skicka åtkomsttoken, men klienter som inte är webbläsare kan skicka dem. När du använder .NET-klienten, kan egenskapen Cookies konfigureras i .WithUrl-anropet för att ange en cookie. Om du använder cookie autentisering från .NET-klienten måste appen dock tillhandahålla ett API för att utbyta autentiseringsdata för en cookie.

Autentisering med ägartoken

Klienten kan tillhandahålla en åtkomsttoken i stället för att använda en cookie. Servern verifierar token och använder den för att identifiera användaren. Den här verifieringen görs endast när anslutningen upprättas. Under anslutningens gång revaliderar inte servern automatiskt för att kontrollera om en token har återkallats.

I JavaScript-klienten kan token anges med hjälp av alternativet accessTokenFactory .

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

I .NET-klienten finns det en liknande AccessTokenProvider-egenskap som kan användas för att konfigurera token:

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

Note

Den åtkomsttokenfunktion som du anger anropas innan varje HTTP-begäran som görs av SignalR. Om du behöver förnya token för att hålla anslutningen aktiv (eftersom den kan upphöra att gälla under anslutningen) gör du det inifrån den här funktionen och returnerar den uppdaterade token.

I standardwebb-API:er skickas bearertoken i en HTTP-header. Dock kan SignalR inte ange dessa huvuden i webbläsare när du använder vissa transporter. När du använder WebSockets och Server-Sent Events överförs token som en frågesträngsparameter.

Inbyggd JWT-autentisering

På servern konfigureras autentisering av ägartoken med hjälp av JWT Bearer-mellanprogrammet:

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

Om du vill se kodkommentar översatta till andra språk än engelska kan du meddela oss i det här GitHub-diskussionsproblemet.

Note

Frågesträngen används i webbläsare vid anslutning med WebSockets och Server-Sent Events på grund av begränsningar i API:erna för webbläsare. När du använder HTTPS skyddas frågesträngsvärdena av TLS-anslutningen. Många servrar loggar dock frågesträngsvärden. Mer information finns i Säkerhetsöverväganden i ASP.NET Core SignalR. SignalR använder rubriker för att överföra token i miljöer som stöder dem (till exempel .NET- och Java-klienter).

Identity JWT-autentisering för server

När du använder Identity Server lägger du till en PostConfigureOptions<TOptions> tjänst i projektet:

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

Registrera tjänsten i Startup.ConfigureServices efter att du har lagt till tjänster för autentisering (AddAuthentication) och autentiseringshanteraren för Identity Server (AddIdentityServerJwt):

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

Cookies jämfört med bäraretoken

Cookies är specifika för webbläsare. Att skicka dem från andra typer av klienter ökar komplexiteten jämfört med att skicka ägartoken. cookie Därför rekommenderas inte autentisering om inte appen bara behöver autentisera användare från webbläsarklienten. Autentisering med ägartoken är den rekommenderade metoden när du använder andra klienter än webbläsarklienten.

Windows authentication

Om Windows-autentisering har konfigurerats i din app SignalR kan du använda den identiteten för att skydda hubbar. Men om du vill skicka meddelanden till enskilda användare måste du lägga till en anpassad användar-ID-provider. Windows-autentiseringssystemet tillhandahåller inte anspråk på "Namnidentifierare". SignalR använder anspråket för att fastställa användarnamnet.

Lägg till en ny klass som implementerar IUserIdProvider och hämtar ett av anspråken från användaren som ska användas som identifierare. Om du till exempel vill använda anspråket "Namn" (som är Windows-användarnamnet i formuläret [Domain]\[Username]) skapar du följande klass:

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

I stället ClaimTypes.Namekan du använda valfritt värde från User (till exempel Windows SID-identifierare och så vidare).

Note

Det värde du väljer måste vara unikt bland alla användare i systemet. Annars kan ett meddelande som är avsett för en användare hamna på en annan användare.

Registrera den här komponenten i din Startup.ConfigureServices metod.

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

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

I .NET-klienten måste Windows-autentisering aktiveras genom att egenskapen anges UseDefaultCredentials :

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

Windows-autentisering stöds i Internet Explorer och Microsoft Edge, men inte i alla webbläsare. I Chrome och Safari misslyckas till exempel försök att använda Windows-autentisering och WebSockets. När Windows-autentiseringen misslyckas försöker klienten återgå till andra transporter som kan fungera.

Använda anspråk för att anpassa identitetshantering

En app som autentiserar användare kan härleda SignalR användar-ID från användaranspråk. Om du vill ange hur SignalR skapar användar-ID:n, implementera IUserIdProvider och registrera implementeringen.

Exempelkoden visar hur du använder anspråk för att välja användarens e-postadress som identifierande egenskap.

Note

Det värde du väljer måste vara unikt bland alla användare i systemet. Annars kan ett meddelande som är avsett för en användare hamna på en annan användare.

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

Kontoregistreringen lägger till ett anspråk med typen ClaimsTypes.Email i ASP.NET identitetsdatabasen.

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

Registrera den här komponenten i din Startup.ConfigureServices.

services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();

Auktorisera användare att komma åt hubbar och hubbmetoder

Som standard kan alla metoder i en hubb anropas av en oautentiserad användare. Använd attributet för hubben för att kräva autentisering AuthorizeAttribute :

[Authorize]
public class ChatHub: Hub
{
}

Du kan använda konstruktorargumenten och egenskaperna för attributet för att begränsa åtkomsten [Authorize] till endast användare som matchar specifika auktoriseringsprinciper. Om du till exempel har en anpassad auktoriseringsprincip som heter MyAuthorizationPolicy kan du se till att endast användare som matchar principen kan komma åt hubben med hjälp av följande kod:

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

Enskilda hubbmetoder kan också använda [Authorize] attributet. Om den aktuella användaren inte matchar principen som tillämpas på metoden returneras ett fel till anroparen:

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

Använda auktoriseringshanterare för att anpassa hubbmetodauktorisering

SignalR tillhandahåller en anpassad resurs till auktoriseringshanterare när en hubbmetod kräver auktorisering. Resursen är en instans av HubInvocationContext. HubInvocationContext innehåller HubCallerContext, namnet på hubbmetoden som anropas och argumenten till hubbmetoden.

Tänk dig ett exempel på ett chattrum som tillåter flera organisationsinloggning via Microsoft Entra-ID. Alla med ett Microsoft-konto kan logga in för att chatta, men endast medlemmar i den ägande organisationen bör kunna förbjuda användare eller visa användarnas chatthistorik. Dessutom kanske vi vill begränsa vissa funktioner från vissa användare. Med hjälp av de uppdaterade funktionerna i ASP.NET Core 3.0 är detta fullt möjligt. Observera hur DomainRestrictedRequirement fungerar som en anpassad IAuthorizationRequirement. Nu när HubInvocationContext resursparametern skickas in kan den interna logiken granska kontexten där hubben anropas och fatta beslut om att tillåta användaren att köra enskilda hubbmetoder.

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

I Startup.ConfigureServiceslägger du till den nya principen och anger det anpassade DomainRestrictedRequirement kravet som en parameter för att skapa DomainRestricted principen.

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

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

I det föregående exemplet är DomainRestrictedRequirement-klassen både en IAuthorizationRequirement och sin egen AuthorizationHandler för det kravet. Det är acceptabelt att dela upp dessa två komponenter i separata klasser för att separera problem. En fördel med exemplets metod är att det inte finns något behov av att mata in AuthorizationHandler under starten, eftersom kravet och hanteraren är samma sak.

Ytterligare resurser