Autenticación y autorización en ASP.NET CoreSignalR

Vea o descargue el código de ejemplo (cómo descargarlo)

Autenticación de usuarios que se conectan a un SignalR centro

SignalRse puede usar con ASP.NET Core autenticación para asociar un usuario a cada conexión. En un centro, se puede acceder a los datos de autenticación desde la propiedad HubConnectionContext.User. La autenticación permite al centro llamar a métodos en todas las conexiones asociadas a un usuario. Para obtener más información, vea Administrar usuarios y grupos en SignalR . Se pueden asociar varias conexiones a un solo usuario.

A continuación se muestra un ejemplo Startup.Configure de que usa y ASP.NET Core SignalR autenticació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?}");
    });
}

Nota

El orden en el que se registra y SignalR se ASP.NET Core middleware de autenticación. Llame siempre UseAuthentication a antes de para que tenga un usuario en UseSignalR SignalR HttpContext .

Nota

Si un token expira durante la vigencia de una conexión, la conexión sigue funcionando. LongPolling y ServerSentEvent las conexiones producirán un error en las solicitudes posteriores si no envían nuevos tokens de acceso.

Nota

Si un token expira durante la vigencia de una conexión, la conexión sigue funcionando de forma predeterminada. LongPolling y ServerSentEvent las conexiones producirán un error en las solicitudes posteriores si no envían nuevos tokens de acceso. Para que las conexiones se cierren cuando expire el token de autenticación, establezca CloseOnAuthenticationExpiration.

En una aplicación basada en explorador, la cookie autenticación permite que las credenciales de usuario existentes fluyan automáticamente a las SignalR conexiones. Cuando se usa el cliente del explorador, no se necesita ninguna configuración adicional. Si el usuario ha iniciado sesión en la aplicación, la SignalR conexión hereda automáticamente esta autenticación.

Cookieson una manera específica del explorador de enviar tokens de acceso, pero los clientes que no son de explorador pueden enviarlos. Cuando se usa el cliente de .NET, la Cookies propiedad se puede configurar en la llamada para proporcionar un .WithUrl cookie . Sin embargo, el uso de la autenticación desde el cliente de .NET requiere que la aplicación proporcione una cookie API para intercambiar datos de autenticación para cookie .

Autenticación por token de portador

El cliente puede proporcionar un token de acceso en lugar de usar cookie . El servidor valida el token y lo usa para identificar al usuario. Esta validación solo se realiza cuando se establece la conexión. Durante la vida de la conexión, el servidor no vuelve a validar automáticamente para comprobar la revocación de tokens.

En el cliente de JavaScript, el token se puede proporcionar mediante la opción accessTokenFactory.

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

En el cliente de .NET, hay una propiedad AccessTokenProvider similar que se puede usar para configurar el token:

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

Nota

Se llama a la función de token de acceso que proporciona antes de cada solicitud HTTP realizada por SignalR . Si necesita renovar el token para mantener activa la conexión (ya que puede expirar durante la conexión), haga esto desde esta función y devuelva el token actualizado.

En las API web estándar, los tokens de portador se envían en un encabezado HTTP. Sin embargo, SignalR no puede establecer estos encabezados en los exploradores cuando se usan algunos transportes. Al usar WebSockets y Server-Sent eventos, el token se transmite como un parámetro de cadena de consulta.

Autenticación JWT integrada

En el servidor, la autenticación de token de portador se configura mediante el middleware de portador 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. 
}

Si quiere que los comentarios de código se traduzcan en más idiomas además del inglés, háganoslo saber en este problema de debate de GitHub.

Nota

La cadena de consulta se usa en los exploradores al conectarse con WebSockets y Server-Sent Events debido a las limitaciones de la API del explorador. Cuando se usa HTTPS, la conexión TLS protege los valores de cadena de consulta. Sin embargo, muchos servidores registra valores de cadena de consulta. Para obtener más información, vea Consideraciones SignalR de seguridad en ASP.NET Core . SignalR usa encabezados para transmitir tokens en entornos que los admiten (como los clientes de .NET y Java).

Identity Autenticación JWT de servidor

Cuando use Identity Servidor, agregue PostConfigureOptions<TOptions> un servicio al proyecto:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
{
    public void PostConfigure(string name, JwtBearerOptions options)
    {
        var originalOnMessageReceived = options.Events.OnMessageReceived;
        options.Events.OnMessageReceived = async context =>
        {
            await originalOnMessageReceived(context);
                
            if (string.IsNullOrEmpty(context.Token))
            {
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                
                if (!string.IsNullOrEmpty(accessToken) && 
                    path.StartsWithSegments("/hubs"))
                {
                    context.Token = accessToken;
                }
            }
        };
    }
}

Registre el servicio en después Startup.ConfigureServices de agregar servicios para la autenticación ( ) y el controlador de AddAuthentication autenticación para Server ( Identity AddIdentityServerJwt ):

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

Cookies frente a tokens de portador

Cookieson específicos de los exploradores. El envío desde otros tipos de clientes agrega complejidad en comparación con el envío de tokens de portador. Por lo tanto, cookie no se recomienda la autenticación a menos que la aplicación solo necesite autenticar a los usuarios desde el cliente del explorador. La autenticación de token de portador es el enfoque recomendado cuando se usan clientes que no son el cliente del explorador.

Autenticación de Windows

Si Windows autenticación está configurada en la aplicación, SignalR puede usar esa identidad para proteger los centros. Sin embargo, para enviar mensajes a usuarios individuales, debe agregar un proveedor de id. de usuario personalizado. El Windows de autenticación no proporciona la notificación "Identificador de nombre". SignalR usa la notificación para determinar el nombre de usuario.

Agregue una nueva clase que implemente y recupere una de las IUserIdProvider notificaciones del usuario que se usará como identificador. Por ejemplo, para usar la notificación "Name" (que es el nombre Windows nombre de usuario con el formato [Domain]\[Username] ), cree la clase siguiente:

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

En lugar de , puede usar cualquier valor de (como el identificador ClaimTypes.Name User Windows SID, y así sucesivamente).

Nota

El valor que elija debe ser único entre todos los usuarios del sistema. De lo contrario, un mensaje destinado a un usuario podría acabar en un usuario diferente.

Registre este componente en el Startup.ConfigureServices método .

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

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

En el cliente de .NET, Windows autenticación debe habilitarse estableciendo la propiedad UseDefaultCredentials:

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

Windows autenticación se admite en Internet Explorer y Microsoft Edge, pero no en todos los exploradores. Por ejemplo, en Chrome y Safari, se produce un error al intentar usar Windows autenticación y WebSockets. Cuando Windows la autenticación, el cliente intenta volver a otros transportes que podrían funcionar.

Uso de notificaciones para personalizar el control de identidades

Una aplicación que autentica a los usuarios puede derivar SignalR los id. de usuario de las notificaciones de usuario. Para especificar cómo SignalR crea los id. de usuario, IUserIdProvider implemente y registre la implementación.

El código de ejemplo muestra cómo usar las notificaciones para seleccionar la dirección de correo electrónico del usuario como propiedad de identificación.

Nota

El valor que elija debe ser único entre todos los usuarios del sistema. De lo contrario, un mensaje destinado a un usuario podría acabar en un usuario diferente.

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

El registro de cuenta agrega una notificación con el tipo a la ClaimsTypes.Email base ASP.NET de datos de identidad.

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

Registre este componente en Startup.ConfigureServices .

services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();

Autorización a los usuarios para acceder a centros y métodos de concentrador

De forma predeterminada, un usuario no autenticado puede llamar a todos los métodos de un centro. Para requerir autenticación, aplique el atributo Authorize al centro:

[Authorize]
public class ChatHub: Hub
{
}

Puede usar los argumentos de constructor y las propiedades del atributo [Authorize] para restringir el acceso solo a los usuarios que coincidan con directivas de autorización específicas. Por ejemplo, si tiene una directiva de autorización personalizada denominada , puede asegurarse de que solo los usuarios que coincidan con esa directiva puedan acceder al centro MyAuthorizationPolicy mediante el código siguiente:

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

Los métodos de concentrador individuales también [Authorize] pueden tener el atributo aplicado. Si el usuario actual no coincide con la directiva aplicada al método , se devuelve un error al autor de la llamada:

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

Uso de controladores de autorización para personalizar la autorización del método central

SignalR proporciona un recurso personalizado a los controladores de autorización cuando un método de concentrador requiere autorización. El recurso es una instancia de HubInvocationContext. incluye , el nombre del método de concentrador que se invoca HubInvocationContext HubCallerContext y los argumentos para el método de concentrador.

Considere el ejemplo de un salón de chat que permite el inicio de sesión de varias organizaciones a través de Azure Active Directory. Cualquier persona con una cuenta Microsoft puede iniciar sesión en chat, pero solo los miembros de la organización propietaria deben poder prohibir a los usuarios o ver los historiales de chat de los usuarios. Además, es posible que desee restringir cierta funcionalidad de determinados usuarios. Con las características actualizadas de ASP.NET Core 3.0, esto es totalmente posible. Observe cómo DomainRestrictedRequirement actúa como un IAuthorizationRequirement personalizado. Ahora que se pasa el parámetro de recurso, la lógica interna puede inspeccionar el contexto en el que se llama al centro y tomar decisiones sobre cómo permitir que el usuario ejecute métodos de HubInvocationContext concentrador individuales.

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

En Startup.ConfigureServices , agregue la nueva directiva, proporcionando el requisito personalizado como parámetro para crear la DomainRestrictedRequirement DomainRestricted directiva.

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

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

En el ejemplo anterior, la DomainRestrictedRequirement clase es y es propia para ese IAuthorizationRequirement AuthorizationHandler requisito. Es aceptable dividir estos dos componentes en clases independientes para separar los problemas. Una ventaja del enfoque del ejemplo es que no es necesario insertar durante el inicio, ya que el requisito y el controlador AuthorizationHandler son lo mismo.

Recursos adicionales