Ověřování a autorizace v ASP.NET CoreSignalR
Zobrazení nebo stažení ukázkového kódu (stažení)
Ověření uživatelů, kteří se připojují 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 jsou ověřovací data přístupná z vlastnosti HubConnectionContext.User. Ověřování umožňuje centru volat metody u všech připojení přidružených k uživateli. Další informace najdete 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, který používá a Startup.Configure SignalR ASP.NET Core 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?}");
});
}
public void Configure(IApplicationBuilder app)
{
...
app.UseStaticFiles();
app.UseAuthentication();
app.UseSignalR(hubs =>
{
hubs.MapHub<ChatHub>("/chat");
});
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
}
Poznámka
Na pořadí, ve kterém zaregistrujete SignalR a , ASP.NET Core middleware ověřování. Vždy UseAuthentication volejte UseSignalR dříve, SignalR aby měl uživatel na HttpContext .
Poznámka
Pokud platnost tokenu vyprší po celou dobu životnosti připojení, připojení bude dál fungovat. LongPolling``ServerSentEventa připojení při následných požadavcích selžou, pokud neposílají nové přístupové tokeny.
Poznámka
Pokud platnost tokenu vyprší po celou dobu životnosti připojení, bude připojení ve výchozím nastavení dál fungovat. LongPolling``ServerSentEventa připojení při následných požadavcích selžou, pokud neposílají nové přístupové tokeny. Pro ukončení připojení po vypršení platnosti ověřovacího tokenu nastavte CloseOnAuthenticationExpiration.
Cookie Ověřování
V aplikaci založené na prohlížeči ověřování umožňuje, aby vaše stávající přihlašovací cookie údaje uživatele automaticky přitékaly k SignalR připojením. Pokud používáte 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ěření automaticky dědí.
Cookiejsou způsob odesílání přístupových tokenů specifický pro prohlížeč, ale klienti mimo prohlížeč je mohou odesílat. Při použití klienta .NETje možné ve volání nakonfigurovat vlastnost tak, Cookies aby .WithUrl poskytovala cookie . Použití ověřování z klienta .NET ale vyžaduje, aby aplikace poskytovala rozhraní API pro cookie výměnu ověřovacích dat pro cookie .
Ověřování pomocí tokenu bearer
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 v případě, že je připojení vytvořeno. Během životnosti připojení se server automaticky znovu neivalituje, aby kontroll odvolání tokenu.
V klientovi JavaScriptu je možné token skytovat 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 je možné 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 poskytnete, se volá před každým požadavkem HTTP, který provedl SignalR . Pokud potřebujete token obnovit, aby bylo připojení aktivní (protože může během připojení vypršet), proveďte to v rámci této funkce a vraťte aktualizovaný token.
Ve standardních webových rozhraních API se v hlavičce PROTOKOLU HTTP odesílaly bearer tokeny. Při SignalR použití některých přenosů ale nemůže tyto hlavičky nastavit v prohlížečích. Při použití protokolu WebSockets a Server-Sent Events se token přenáší jako parametr řetězce dotazu.
Integrované ověřování JWT
Na serveru se konfiguruje ověřování pomocí bearerového middlewaru JWT:
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 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ž angličtiny, dejte nám vědět v tomto problému diskuze na GitHubu.
Poznámka
Řetězec dotazu se používá v prohlížečích při připojování pomocí protokolu WebSocket a Server-Sent události kvůli omezením rozhraní API prohlížeče. Při použití protokolu HTTPS jsou hodnoty řetězce dotazu zabezpečeny připojením TLS. Mnoho serverů ale protokolul hodnoty řetězce dotazu. Další informace najdete v tématu Aspekty zabezpečení SignalR v ASP.NET Core . SignalR používá hlavičky k přenosu tokenů v prostředích, která je podporují (například klienti .NET a Java).
Identity Ověřování JWT serveru
Pokud používáte Identity Server, PostConfigureOptions<TOptions> přidejte 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;
}
}
};
}
}
Po přidání služeb pro ověřování ( ) zaregistrujte službu v a obslužnou rutinu Startup.ConfigureServices ověřování pro Server ( AddAuthentication Identity AddIdentityServerJwt ):
services.AddAuthentication()
.AddIdentityServerJwt();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>,
ConfigureJwtBearerOptions>());
Cookies vs. bearer tokens
CookieJsou specifické pro prohlížeče. Odesílání z jiných druhů klientů je v porovnání s odesíláním bearerových tokenů složitější. V důsledku toho se ověřování nedoporučuje, pokud aplikace nepotřebuje jenom cookie ověřovat uživatele z klienta prohlížeče. Doporučeným přístupem při použití jiných klientů než klienta prohlížeče je ověřování pomocí tokenu bearer.
Ověřování systému Windows
Pokud Windows ve vaší aplikaci nakonfigurované ověřování, můžete tuto identitu použít k zabezpečení SignalR center. Pokud ale chcete odesílat zprávy jednotlivým uživatelům, musíte přidat vlastního zprostředkovatele ID uživatele. Systém Windows ověřování 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 a načítá jednu z deklarací identity od IUserIdProvider uživatele, která se má použít jako identifikátor. Pokud například chcete použít deklaraci identity "Name" (což je Windows uživatelské jméno ve tvaru [Domain]\[Username] ), vytvořte následující třídu:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
Místo můžete použít libovolnou hodnotu z objektu (například Windows ClaimTypes.Name identifikátor User SID atd.).
Poznámka
Hodnota, kterou zvolíte, musí být jedinečná mezi všemi uživateli ve vašem systému. Jinak by zpráva určená pro jednoho uživatele mohla být určena jinému uživateli.
Zaregistrujte tuto komponentu v Startup.ConfigureServices metodě .
public void ConfigureServices(IServiceCollection services)
{
// ... other services ...
services.AddSignalR();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
}
V klientovi .NET musí být Windows ověřování povoleno nastavením vlastnosti UseDefaultCredentials:
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chathub", options =>
{
options.UseDefaultCredentials = true;
})
.Build();
Windows ověřování se podporuje ve Internet Explorer a Microsoft Edge, ale ne ve všech prohlížečích. Například v Prohlížeči Chrome a Safari dojde k selhání pokusu o Windows a protokolu WebSocket. Když Windows ověřování selže, klient se pokusí vrátit k jiným přenosům, které můžou fungovat.
Přizpůsobení zpracování identit pomocí deklarací identity
Aplikace, která ověřuje uživatele, může SignalR odvozovat ID uživatelů z deklarací identity uživatelů. Pokud chcete SignalR určit, jak se vytvářejí ID uživatelů, IUserIdProvider implementujte a zaregistrujte implementaci.
Ukázkový kód ukazuje, jak můžete pomocí deklarací identity vybrat e-mailovou adresu uživatele jako identifikující vlastnost.
Poznámka
Hodnota, kterou zvolíte, musí být jedinečná mezi všemi uživateli ve vašem systému. Jinak by zpráva určená pro jednoho uživatele mohla být určena jinému uživateli.
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 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 v souboru Startup.ConfigureServices .
services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
Autorizace uživatelů pro přístup k centrem a metodám centra
Ve výchozím nastavení může neověřený uživatel volat všechny metody v centru. Pokud chcete vyžadovat ověření, použijte na centrum atribut Authorize:
[Authorize]
public class ChatHub: Hub
{
}
Pomocí argumentů konstruktoru a vlastností atributu můžete omezit přístup pouze na uživatele odpovídající [Authorize] konkrétním zásadám autorizace. Pokud máte například vlastní zásady autorizace s názvem , 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 MyAuthorizationPolicy zásadám:
[Authorize("MyAuthorizationPolicy")]
public class ChatHub : Hub
{
}
Jednotlivé metody centra mohou mít [Authorize] také použitý atribut . Pokud aktuální uživatel neodpovídá zásadám použitým na metodu , volajícímu se vrátí 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) ...
}
}
Přizpůsobení autorizace metody centra pomocí obslužných rutin autorizace
SignalR poskytuje vlastní prostředek pro obslužné rutiny autorizace, když metoda centra vyžaduje autorizaci. Prostředek je instance HubInvocationContext . zahrnuje , název vyvolané metody centra a HubInvocationContext HubCallerContext argumenty metody centra.
Představte si příklad chatovací místnosti umožňující přihlášení více organizací přes Azure Active Directory. Kdokoli s účet Microsoft se může přihlásit ke chatu, ale jenom členové vlastnící organizace by měli být schopni zakázat uživatele nebo zobrazit historii chatu 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 DomainRestrictedRequirement si, jak slouží jako vlastní IAuthorizationRequirement . Teď, když se předává parametr prostředku, může interní logika zkontrolovat kontext, ve kterém se volá centrum, a rozhodnout se, jak uživateli umožnit spouštění jednotlivých metod HubInvocationContext 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));
}
}
V souboru přidejte novou zásadu, která jako parametr pro vytvoření zásady poskytne Startup.ConfigureServices DomainRestrictedRequirement vlastní DomainRestricted požadavek.
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 je třída pro tento požadavek vlastní třídou DomainRestrictedRequirement IAuthorizationRequirement i AuthorizationHandler . Tyto dvě komponenty je přijatelné rozdělit do samostatných tříd, abyste oddělili obavy. Výhodou přístupu v tomto příkladu je, že při spuštění není potřeba vpouštět , protože požadavek a obslužná rutina AuthorizationHandler jsou stejné.