Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Artikel wird beschrieben, wie Sie Benutzer in ASP.NET Core Anwendungen mit SignalR authentifizieren und autorisieren.
Authentifizieren von Benutzern, die eine Verbindung mit einem SignalR-Hub herstellen
SignalR kann mit ASP.NET Core-Authentifizierung verwendet werden, um jeder Verbindung einen Benutzer zuzuordnen. In einem Hub kann über die HubConnectionContext.User-Eigenschaft auf Authentifizierungsdaten zugegriffen werden. Authentifizierung ermöglicht es dem Hub, Methoden für alle Verbindungen aufzurufen, die einem Benutzer zugeordnet sind. Weitere Informationen finden Sie unter Verwalten von Benutzern und Gruppen in SignalR. Mehrere Verbindungen können einem einzelnen Benutzer zugeordnet werden.
Der folgende Code ist ein Beispiel, das SignalR und ASP.NET Core-Authentifizierung verwendet:
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
Wenn ein Token während der Lebensdauer einer Verbindung abläuft, funktioniert die Verbindung standardmäßig weiterhin.
LongPolling- und ServerSentEvents-Verbindungen schlagen bei nachfolgenden Anforderungen fehl, wenn sie keine neuen Zugriffstoken senden. Damit Verbindungen geschlossen werden, wenn das Authentifizierungstoken abläuft, legen Sie die Option CloseOnAuthenticationExpiration fest.
Benutzer- und Rollenänderungen während der Verbindungslebensdauer
SignalR erfasst den authentifizierten Benutzer, wenn eine Verbindung hergestellt wird, und speichert sie für die Lebensdauer der Verbindung zwischen. Der zwischengespeicherte Prinzipal steht Hubmethoden über die Eigenschaft Context.User (HubCallerContext.User) zur Verfügung und wird verwendet, um Aufrufe von Hubmethoden zu autorisieren.
SignalR aktualisiert den Benutzer während der Lebensdauer der Verbindung nicht automatisch, unabhängig vom Authentifizierungsschema. Dieses Verhalten gilt für alle Schemas, einschließlich cookie Authentifizierung und Bearertokenauthentifizierung.
Änderungen an der Identität, den Rollen oder den Claims eines Benutzers, die nach dem Herstellen der Verbindung auftreten, werden in einer bestehenden Verbindung nicht berücksichtigt. Dieses Verhalten gilt auch dann, wenn der zugrunde liegende Transport neue HTTP-Anforderungen wie z. B. die LongPolling und ServerSentEvents transporte vorschreibt. Obwohl die ASP.NET Core-Authentifizierungsmiddleware jede dieser HTTP-Anforderungen erneut authentifiziert, verwendet SignalR weiterhin den Prinzipal, der beim Herstellen der Verbindung zwischengespeichert wurde.
Betrachten Sie das folgende Szenario:
- Eine App verwendet die cookie-Authentifizierung und den
LongPolling-Transport. Das gleiche Verhalten gilt für die Bearertokenauthentifizierung und denServerSentEventsTransport. - Ein Benutzer ist mit der
EditorRolle angemeldet und verfügt über eine offene SignalR Verbindung. - Die App entfernt die
EditorRolle von diesem Benutzer.
Bei der nächsten Anforderung mit langer Abrufzeit kann die Authentifizierungsmiddleware den Benutzer ohne die Rolle Editor authentifizieren. Dies kann beispielsweise passieren, wenn Rollen aus einem Datenspeicher geladen werden oder das cookie aktualisiert wird. Der Hub autorisiert die Hubmethodenaufrufe des Benutzers für [Authorize(Roles = "Editor")] jedoch weiterhin, da Context.User (HubCallerContext.User) weiterhin den Prinzipal enthält, der vor dem Entfernen der Rolle zwischengespeichert wurde. Der Nutzer kann weiterhin ausschließlich Hubmethoden für Editor aufrufen, bis die Verbindung geschlossen wird.
Um eine aktualisierte Autorisierung für eine aktive Verbindung zu erzwingen, führen Sie einen der folgenden Ansätze aus:
- Schließen Sie betroffene Verbindungen, sodass Clients erneut eine Verbindung herstellen und erneut authentifizieren können. Bei der Bearertokenauthentifizierung schließt die CloseOnAuthenticationExpiration-Option Verbindungen, wenn das Authentifizierungstoken abläuft.
- Führen Sie Autorisierungsprüfungen in Hubmethoden für aktuelle Daten durch, z. B. die aktuellen Rollen oder Ansprüche des Benutzers aus einem Datenspeicher, anstatt nur auf den zwischengespeicherten Prinzipal zu vertrauen.
Cookie-Authentifizierung
In einer browserbasierten App ermöglicht cookie-Authentifizierung, dass vorhandene Benutzeranmeldeinformationen automatisch in SignalR-Verbindungen einfließen. Wenn der Browserclient verwendet wird, ist keine zusätzliche Konfiguration erforderlich. Wenn der Benutzer bei einer App angemeldet ist, erbt die SignalR Verbindung diese Authentifizierung automatisch.
Cookies sind eine browserspezifische Methode zum Senden von Zugriffstoken, aber nichtbrowser-Clients können sie senden. Wenn der .NET Client verwendet wird, kann die eigenschaft Cookies im aufruf .WithUrl konfiguriert werden, um einen cookie bereitzustellen. Die Verwendung von cookie-Authentifizierung über den .NET-Client erfordert jedoch, dass die App eine API zum Austauschen von Authentifizierungsdaten für ein cookie bereitstellt.
Von Bedeutung
Ab ASP.NET Core 10 leiten bekannte API-Endpunkte bei Nutzung der cookie-Authentifizierung nicht mehr zu Anmeldeseiten um. Stattdessen geben sie 401/403-Statuscodes zurück. Ausführliche Informationen finden Sie unter API-Endpunktauthentifizierungsverhalten in ASP.NET Core.
Bearertokenauthentifizierung
Der Client kann ein Zugriffstoken bereitstellen, anstatt ein cookie zu verwenden. Der Server überprüft das Token und verwendet es zum Identifizieren des Benutzers. Bei Datentransporten, die mehrere HTTP-Anforderungen stellen (z. B. LongPolling und ServerSentEvents), wird die Authentifizierung bei jeder Anforderung ausgeführt, aber SignalR speichert den resultierenden Prinzipal für die Dauer der Verbindung im Zwischenspeicher. Wie bei der Authentifizierung mit cookie führt SignalR während der Verbindungsdauer keine automatische erneute Validierung des Benutzers durch, um zu prüfen, ob das Token widerrufen wurde oder ob sich die Rollen oder Claims des Benutzers geändert haben. Weitere Informationen finden Sie unter Benutzer- und Rollenänderungen während der Lebensdauer der Verbindung.
Im JavaScript-Client kann das Token mithilfe der AccessTokenFactory-Option bereitgestellt werden.
// Connect, using the token we got.
this.connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/chat", { accessTokenFactory: () => this.loginToken })
.build();
Im .NET-Client gibt es eine ähnliche AccessTokenProvider-Eigenschaft, die zum Konfigurieren des Tokens verwendet werden kann:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chathub", options =>
{
options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
})
.Build();
Note
Die Zugriffstokenfunktion wird vor jeder HTTP-Anfrage aufgerufen, die von SignalR ausgeführt wird. Wenn das Token erneuert werden muss, um die Verbindung aktiv zu halten, führen Sie die Erneuerung aus dieser Funktion aus, und geben Sie das aktualisierte Token zurück. Das Token muss möglicherweise erneuert werden, damit es während der Verbindung nicht abläuft.
In Standardweb-APIs werden Bearertoken in einem HTTP-Header gesendet. SignalR kann diese Header jedoch in Browsern nicht setzen, wenn einige Transportmethoden verwendet werden. Wenn WebSockets und Server-Sent Events verwendet werden, wird das Token als Query-String-Parameter übertragen.
Integrierte JWT-Authentifizierung
Auf dem Server wird die Bearertokenauthentifizierung mithilfe des JSON-Webtokens (JWT) Bearer Middleware konfiguriert:
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
Die Abfragezeichenfolge wird in Browsern verwendet, wenn eine Verbindung mit WebSockets und Server-Sent Events aufgrund von Browser-API-Einschränkungen hergestellt wird. Wenn Sie HTTPS verwenden, sichert die TLS-Verbindung die Abfragezeichenfolgenwerte. Viele Server protokollieren jedoch Abfragezeichenfolgenwerte. Weitere Informationen finden Sie unter Sicherheitsüberlegungen zu ASP.NET CoreSignalR. SignalR verwendet Header, um Token in Umgebungen zu übertragen, die sie unterstützen, z. B. die .NET und Java Clients.
Identity Server-JWT-Authentifizierung
Fügen Sie bei Verwendung von Duende IdentityServer dem Projekt einen PostConfigureOptions<TOptions> Dienst hinzu:
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;
}
}
};
}
}
Registrieren Sie den Dienst nach dem Hinzufügen von Diensten für die Authentifizierung (mit der AddAuthentication Methode) und dem Authentifizierungshandler für Identity Server (mit der AddIdentityServerJwt Methode):
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 im Vergleich zu Bearer-Token
Cookies sind spezifisch für Browser. Das Senden von Authentifizierungs-Token über andere Arten von Clients erhöht die Komplexität im Vergleich zum Versenden von Bearer-Token. Cookie-Authentifizierung wird nur empfohlen, wenn die App nur Benutzer über den Browserclient authentifizieren muss. Bearertokenauthentifizierung ist der empfohlene Ansatz, wenn andere Clients als der Browserclient verwendet werden.
Windows-Authentifizierung
Wenn Windows-Authentifizierung in der App konfiguriert ist, kann SignalR diese Identität zum Sichern von Hubs verwenden. Fügen Sie jedoch einen benutzerdefinierten Benutzer-ID-Anbieter hinzu, um Nachrichten an einzelne Benutzer zu senden. Das Windows-Authentifizierungssystem stellt den Anspruch „Namenbezeichner“ nicht bereit. SignalR verwendet den Anspruch, um den Benutzernamen zu bestimmen.
Fügen Sie eine neue Klasse hinzu, die IUserIdProvider implementiert, und rufen Sie einen der Ansprüche des Benutzers ab, um ihn als Bezeichner zu verwenden. Um beispielsweise den Anspruch „Name“ zu verwenden (der Windows-Benutzername im Format [Domain]/[Username]), erstellen Sie die folgende Klasse:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
Verwenden Sie anstelle von ClaimTypes.Name einen beliebigen Wert aus dem User, z. B. den Windows SID-Bezeichner usw.
Note
Der angegebene Wert muss für alle Benutzer im System eindeutig sein. Andernfalls erreicht eine nachricht, die für einen Benutzer vorgesehen ist, möglicherweise einen anderen Benutzer.
Registrieren Sie diese Komponente in der datei 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.
Im .NET Client muss Windows-Authentifizierung aktiviert sein, indem die Eigenschaft UseDefaultCredentials festgelegt wird:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chathub", options =>
{
options.UseDefaultCredentials = true;
})
.Build();
Windows-Authentifizierung wird in Microsoft Edge unterstützt, aber nicht in allen Browsern. In Chrome und Safari schlägt der Versuch beispielsweise fehl, Windows-Authentifizierung und WebSockets zu verwenden. Wenn Windows-Authentifizierung fehlschlägt, versucht der Client, auf andere Transporte zurückzugreifen, was möglicherweise funktioniert.
Ansprüche nutzen, um die Identitätsverwaltung anzupassen
Eine App, die Benutzer authentifiziert, kann SignalR-Benutzer-IDs von Benutzeransprüchen ableiten. Implementieren Sie SignalR, und registrieren Sie die Implementierung, um anzugeben, wie IUserIdProvider Benutzer-IDs erstellt.
Der Beispielcode zeigt, wie Sie mithilfe von Ansprüchen die E-Mail-Adresse des Benutzers als identifizierende Eigenschaft auswählen.
Note
Der angegebene Wert muss für alle Benutzer im System eindeutig sein. Andernfalls erreicht eine nachricht, die für einen Benutzer vorgesehen ist, möglicherweise einen anderen Benutzer.
public class EmailBasedUserIdProvider : IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(ClaimTypes.Email)?.Value!;
}
}
Die Kontoregistrierung fügt der ASP.NET Identitätsdatenbank einen Anspruch mit typ ClaimsTypes.Email hinzu.
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.
Registrieren Sie diese Komponente in der datei Program.cs :
builder.Services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
Autorisieren von Benutzern für den Zugriff auf Hubs und Hubmethoden
Standardmäßig kann ein nicht authentifizierter Benutzer alle Methoden in einem Hub aufrufen. Wenden Sie das AuthorizeAttribute-Attribut auf den Hub an, um Authentifizierung zu verlangen:
[Authorize]
public class ChatHub: Hub
{
}
Die Konstruktorargumente und -eigenschaften des [Authorize]-Attributs können verwendet werden, um den Zugriff auf Benutzer zu beschränken, die bestimmten Autorisierungsrichtlinien entsprechen. Mit der benutzerdefinierten Autorisierungsrichtlinie namens MyAuthorizationPolicy können beispielsweise nur Benutzer, die dieser Richtlinie entsprechen, mithilfe des folgenden Codes auf den Hub zugreifen:
[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.
Das [Authorize]-Attribut kann auf einzelne Hubmethoden angewendet werden. Wenn der aktuelle Benutzer nicht der für die Methode geltenden Richtlinie entspricht, wird ein Fehler an den Aufrufer zurückgegeben:
[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) ...
}
}
Verwenden von Autorisierungshandlern zum Anpassen der Hubmethodenautorisierung
SignalR bietet eine benutzerdefinierte Ressource für Autorisierungshandler, wenn eine Hubmethode Autorisierung erfordert. Die Ressource ist eine Instanz von HubInvocationContext.
HubInvocationContext enthält HubCallerContext, den Namen der aufgerufenen Hubmethode, sowie die Argumente für die Hubmethode.
Betrachten Sie das Beispiel eines Chatrooms, der es mehreren Organisationen ermöglicht, sich über Microsoft Entra ID anzumelden. Jede Person mit einem Microsoft-Konto kann sich beim Chat anmelden, aber nur Mitglieder der besitzenden Organisation sollte in der Lage sein, Benutzer zu sperren oder den Chatverlauf anderer Benutzer anzuzeigen. Es kann auch erforderlich sein, einige Funktionen von bestimmten Benutzern einzuschränken. Beachten Sie in diesem Szenario, wie DomainRestrictedRequirement als benutzerdefiniertes IAuthorizationRequirement dient. Da der HubInvocationContext Ressourcenparameter übergeben wird, kann die interne Logik den Kontext prüfen, in dem der Hub aufgerufen wird, und Entscheidungen treffen, mit denen der Benutzer einzelne Hubmethoden ausführen kann:
[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));
}
}
Fügen Sie in der datei Program.cs die neue Richtlinie hinzu, die die benutzerdefinierte DomainRestrictedRequirement Anforderung als Parameter zum Erstellen der DomainRestricted Richtlinie bereitstellt:
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.
Im vorherigen Beispiel ist die DomainRestrictedRequirement-Klasse sowohl ein IAuthorizationRequirement als auch ein eigener AuthorizationHandler für diese Anforderung. Es ist zulässig, diese beiden Komponenten in separate Klassen aufzuteilen, um die Verantwortlichkeiten zu trennen. Der Ansatz in diesem Beispiel bietet den Vorteil, dass das AuthorizationHandler beim Start nicht injiziert werden muss, da die Anforderung und der Handler ein und dasselbe sind.
Verwandte Inhalte
Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)
Authentifizieren von Benutzern, die eine Verbindung mit einem SignalR-Hub herstellen
SignalR kann mit ASP.NET Core-Authentifizierung verwendet werden, um jeder Verbindung einen Benutzer zuzuordnen. In einem Hub kann über die HubConnectionContext.User-Eigenschaft auf Authentifizierungsdaten zugegriffen werden. Authentifizierung ermöglicht es dem Hub, Methoden für alle Verbindungen aufzurufen, die einem Benutzer zugeordnet sind. Weitere Informationen finden Sie unter Verwalten von Benutzern und Gruppen in SignalR. Einem einzelnen Benutzer können mehrere Verbindungen zugeordnet werden.
Nachfolgend finden Sie ein Beispiel für Startup.Configure, das SignalR- und ASP.NET Core-Authentifizierung verwendet:
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
Wenn ein Token während der Lebensdauer einer Verbindung abläuft, funktioniert die Verbindung weiterhin.
LongPolling- und ServerSentEvents-Verbindungen schlagen bei nachfolgenden Anforderungen fehl, wenn sie keine neuen Zugriffstoken senden.
Cookie-Authentifizierung
In einer browserbasierten App ermöglicht cookie-Authentifizierung, dass Ihre vorhandenen Benutzeranmeldeinformationen automatisch in SignalR-Verbindungen einfließen. Bei Verwendung des Browserclients ist keine zusätzliche Konfiguration erforderlich. Wenn der Benutzer bei Ihrere App angemeldet ist, erbt die SignalR-Verbindung automatisch diese Authentifizierung.
Cookies sind eine browserspezifische Methode zum Senden von Zugriffstoken, aber Nicht-Browserclients können sie senden. Bei Verwendung des .NET-Clients kann die Cookies-Eigenschaft im .WithUrl-Aufruf so konfiguriert werden, dass ein cookie bereitgestellt wird. Die Verwendung von cookie-Authentifizierung über den .NET-Client erfordert jedoch, dass die App eine API zum Austauschen von Authentifizierungsdaten für ein cookie bereitstellt.
Bearertokenauthentifizierung
Der Client kann ein Zugriffstoken bereitstellen, anstatt ein cookie zu verwenden. Der Server überprüft das Token und verwendet es zum Identifizieren des Benutzers. Diese Überprüfung erfolgt nur, wenn die Verbindung hergestellt wird. Während der Dauer der Verbindung führt der Server keine automatische erneute Überprüfung durch, um festzustellen, ob das Token widerrufen wurde.
Im JavaScript-Client kann das Token mithilfe der Option accessTokenFactory bereitgestellt werden.
// Connect, using the token we got.
this.connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/chat", { accessTokenFactory: () => this.loginToken })
.build();
Im .NET-Client gibt es eine ähnliche AccessTokenProvider-Eigenschaft, die zum Konfigurieren des Tokens verwendet werden kann:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chathub", options =>
{
options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
})
.Build();
Note
Die von Ihnen bereitgestellte Zugriffstokenfunktion wird vor jeder HTTP-Anforderung aufgerufen, die von SignalR vorgenommen wird. Wenn Sie das Token erneuern müssen, um die Verbindung aktiv zu halten (da sie während der Verbindung möglicherweise abläuft), führen Sie dies innerhalb dieser Funktion aus, und geben Sie das aktualisierte Token zurück.
In Standardweb-APIs werden Bearertoken in einem HTTP-Header gesendet. SignalR kann diese Header jedoch nicht in Browsern festlegen, wenn gewisse Transporte verwendet werden. Bei Verwendung von WebSockets und Server-Sent Events wird das Token als Abfragezeichenfolgenparameter übertragen.
Integrierte JWT-Authentifizierung
Auf dem Server wird die Bearer-Token-Authentifizierung unter Verwendung der JWT Bearer-Middleware konfiguriert.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
// Identity made Cookie authentication the default.
// However, we want JWT Bearer Auth to be the default.
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// Configure the Authority to the expected value for your authentication provider
// This ensures the token is appropriately validated
options.Authority = /* TODO: Insert Authority URL here */;
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
// Sending the access token in the query string is required when using WebSockets or ServerSentEvents
// due to a limitation in Browser APIs. We restrict it to only calls to the
// SignalR hub in this code.
// See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
// for more information about security considerations when using
// the query string to transmit the access token.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
// Change to use Name as the user identifier for SignalR
// WARNING: This requires that the source of your JWT token
// ensures that the Name claim is unique!
// If the Name claim isn't unique, users could receive messages
// intended for a different user!
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
// Change to use email as the user identifier for SignalR
// services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
// WARNING: use *either* the NameUserIdProvider *or* the
// EmailBasedUserIdProvider, but do not use both.
}
Note
Die Abfragezeichenfolge wird in Browsern verwendet, wenn eine Verbindung mit WebSockets und Server-Sent Events aufgrund von Browser-API-Einschränkungen hergestellt wird. Bei Verwendung von HTTPS werden Abfragezeichenfolgenwerte durch die TLS-Verbindung gesichert. Viele Server protokollieren jedoch Abfragezeichenfolgenwerte. Weitere Informationen finden Sie unter Sicherheitsüberlegungen zu ASP.NET CoreSignalR. SignalR verwendet Header, um Token in Umgebungen zu übertragen, die diese unterstützen (z. B. die .NET- und Java-Clients).
Identity Server-JWT-Authentifizierung
Wenn Sie Identity-Server verwenden, fügen Sie dem Projekt einen PostConfigureOptions<TOptions>-Dienst hinzu:
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;
}
}
};
}
}
Registrieren Sie den Dienst in Startup.ConfigureServices nach dem Hinzufügen von Diensten für die Authentifizierung (AddAuthentication) und dem Authentifizierungshandler für Identity-Server (AddIdentityServerJwt):
services.AddAuthentication()
.AddIdentityServerJwt();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>,
ConfigureJwtBearerOptions>());
Cookies im Vergleich zu Bearer-Token
Cookies sind spezifisch für Browser. Das Senden von Authentifizierungs-Token über andere Arten von Clients erhöht die Komplexität im Vergleich zum Versenden von Bearer-Token. Daher wird die cookie-Authentifizierung nicht empfohlen, es sei denn, die App muss Benutzer ausschließlich vom Browser-Client aus authentifizieren. Bearertokenauthentifizierung ist der empfohlene Ansatz, wenn andere Clients als der Browserclient verwendet werden.
Windows-Authentifizierung
Wenn Windows-Authentifizierung in Ihrer App konfiguriert ist, können SignalR diese Identität verwenden, um Hubs zu schützen. Um jedoch Nachrichten an einzelne Benutzer zu senden, müssen Sie einen benutzerdefinierten Benutzer-ID-Anbieter hinzufügen. Das Windows-Authentifizierungssystem stellt den Anspruch „Namenbezeichner“ nicht bereit. SignalR verwendet den Anspruch, um den Benutzernamen zu bestimmen.
Fügen Sie eine neue Klasse hinzu, die IUserIdProvider implementiert, und rufen Sie einen der Ansprüche vom Benutzer ab, um ihn als Bezeichner zu verwenden. Um beispielsweise den Anspruch „Name“ zu verwenden (der Windows-Benutzername im Format [Domain]\[Username]), erstellen Sie die folgende Klasse:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
Anstelle von ClaimTypes.Name können Sie einen beliebigen Wert aus User verwenden (z. B. den Windows-SID-Bezeichner usw.).
Note
Der von Ihnen ausgewählte Wert muss für alle Benutzer in Ihrem System eindeutig sein. Andernfalls kann eine Nachricht, die für einen Benutzer bestimmt ist, ggf. an einen anderen Benutzer gesendet werden.
Registrieren Sie diese Komponente in Ihrer Startup.ConfigureServices-Methode.
public void ConfigureServices(IServiceCollection services)
{
// ... other services ...
services.AddSignalR();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
}
Im .NET-Client muss Windows-Authentifizierung durch Festlegen der UseDefaultCredentials-Eigenschaft aktiviert werden:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chathub", options =>
{
options.UseDefaultCredentials = true;
})
.Build();
Windows-Authentifizierung wird in Internet Explorer und Microsoft Edge unterstützt, aber nicht in allen Browsern. In Chrome und Safari schlägt der Versuch beispielsweise fehl, Windows-Authentifizierung und WebSockets zu verwenden. Wenn Windows-Authentifizierung fehlschlägt, versucht der Client, auf andere Transporte zurückzugreifen, die möglicherweise funktionieren.
Ansprüche nutzen, um die Identitätsverwaltung anzupassen
Eine App, die Benutzer authentifiziert, kann SignalR-Benutzer-IDs von Benutzeransprüchen ableiten. Implementieren Sie SignalR, und registrieren Sie die Implementierung, um anzugeben, wie IUserIdProvider Benutzer-IDs erstellt.
Der Beispielcode zeigt, wie Sie mithilfe von Ansprüchen die E-Mail-Adresse des Benutzers als identifizierende Eigenschaft auswählen können.
Note
Der von Ihnen ausgewählte Wert muss für alle Benutzer in Ihrem System eindeutig sein. Andernfalls kann eine Nachricht, die für einen Benutzer bestimmt ist, ggf. an einen anderen Benutzer gesendet werden.
public class EmailBasedUserIdProvider : IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
}
}
Die Kontoregistrierung fügt der ASP.NET Identitätsdatenbank einen Anspruch mit typ ClaimsTypes.Email hinzu.
// 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));
Registrieren Sie bitte diese Komponente in Ihrem Startup.ConfigureServices.
services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
Autorisieren von Benutzern für den Zugriff auf Hubs und Hubmethoden
Standardmäßig können alle Methoden in einem Hub von einem nicht authentifizierten Benutzer aufgerufen werden. Wenden Sie das AuthorizeAttribute-Attribut auf den Hub an, um Authentifizierung zu verlangen:
[Authorize]
public class ChatHub: Hub
{
}
Sie können die Konstruktorargumente und -eigenschaften des [Authorize]-Attributs verwenden, um den Zugriff auf Benutzer zu beschränken, die bestimmten Autorisierungsrichtlinien entsprechen. Wenn Sie z. B. eine benutzerdefinierte Autorisierungsrichtlinie namens MyAuthorizationPolicy besitzen, können Sie sicherstellen, dass nur Benutzer, die dieser Richtlinie entsprechen, mit dem folgenden Code auf den Hub zugreifen können:
[Authorize("MyAuthorizationPolicy")]
public class ChatHub : Hub
{
}
Auf einzelne Hubmethoden kann auch das Attribut [Authorize] angewendet werden. Wenn der aktuelle Benutzer nicht der für die Methode geltenden Richtlinie entspricht, wird ein Fehler an den Aufrufer zurückgegeben:
[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) ...
}
}
Verwenden von Autorisierungshandlern zum Anpassen der Hubmethodenautorisierung
SignalR bietet eine benutzerdefinierte Ressource für Autorisierungshandler, wenn eine Hubmethode Autorisierung erfordert. Die Ressource ist eine Instanz von HubInvocationContext.
HubInvocationContext enthält HubCallerContext, den Namen der aufgerufenen Hubmethode, sowie die Argumente für die Hubmethode.
Sehen Sie sich das Beispiel für einen Chatroom an, der mehreren Mitgliedern einer Organisation die Anmeldung über Microsoft Entra ID ermöglicht. Jede Person mit einem Microsoft-Konto kann sich beim Chat anmelden, aber nur Mitglieder der besitzenden Organisation sollte in der Lage sein, Benutzer zu sperren oder den Chatverlauf anderer Benutzer anzuzeigen. Darüber hinaus können Sie bestimmte Funktionen für bestimmte Benutzer einschränken. Mit den aktualisierten Features in ASP.NET Core 3.0 ist dies durchaus möglich. Beachten Sie, dass DomainRestrictedRequirement als benutzerdefinierte IAuthorizationRequirement dient. Nachdem der HubInvocationContext-Ressourcenparameter übergeben wurde, kann die interne Logik den Kontext untersuchen, in dem der Hub aufgerufen wird, und Entscheidungen treffen, ob der Benutzer einzelne Hubmethoden ausführen darf.
[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));
}
}
Fügen Sie in Startup.ConfigureServices die neue Richtlinie hinzu, und geben Sie dabei die benutzerdefinierte DomainRestrictedRequirement-Anforderung als Parameter an, um die DomainRestricted-Richtlinie zu erstellen.
public void ConfigureServices(IServiceCollection services)
{
// ... other services ...
services
.AddAuthorization(options =>
{
options.AddPolicy("DomainRestricted", policy =>
{
policy.Requirements.Add(new DomainRestrictedRequirement());
});
});
}
Im vorherigen Beispiel ist die DomainRestrictedRequirement-Klasse sowohl ein IAuthorizationRequirement als auch ein eigener AuthorizationHandler für diese Anforderung. Es ist zulässig, diese beiden Komponenten in separate Klassen aufzuteilen, um die Verantwortlichkeiten zu trennen. Ein Vorteil des Ansatzes des Beispiels besteht darin, dass AuthorizationHandler beim Start nicht injiziert werden müssen, weil die Anforderung und der Handler ein und dasselbe sind.