Autenticación y autorización de los concentradores de SignalR

por Patrick Fletcher, Tom FitzMacken

Advertencia

Esta documentación no se aplica a la última versión de SignalR. Eche un vistazo a SignalR de ASP.NET Core.

En este tema se describe cómo restringir qué usuarios o roles pueden acceder a los métodos del centro de conectividad.

Versiones de software empleadas en este tema

Versiones anteriores de este tema

Para obtener información sobre versiones anteriores de SignalR, consulte Versiones anteriores de SignalR.

Preguntas y comentarios

Deje sus comentarios sobre este tutorial y sobre lo que podríamos mejorar en los comentarios en la parte inferior de la página. Si tiene alguna pregunta que no esté directamente relacionadas con el tutorial, puede publicarla en el foro de ASP.NET SignalR o en StackOverflow.com.

Información general

Este tema contiene las siguientes secciones:

Atributo Authorize

SignalR proporciona el atributo Authorize para especificar qué usuarios o roles tienen acceso a un centro de conectividad o método. Este atributo está ubicado en el espacio de nombres Microsoft.AspNet.SignalR. Puede aplicar el atributo Authorize a un centro de conectividad o a determinados métodos de un centro de conectividad. Cuando se aplica el atributo Authorize a una clase de centro de conectividad, el requisito de autorización especificado se aplica a todos los métodos del centro de conectividad. En este tema se proporcionan ejemplos de los distintos tipos de requisitos de autorización que puede aplicar. Sin el atributo Authorize, un cliente conectado puede acceder a cualquier método público en el centro de conectividad.

Si ha definido un rol llamado "Admin" en su aplicación web, podría especificar que solo los usuarios con ese rol pueden acceder a un centro de conectividad con el siguiente código.

[Authorize(Roles = "Admin")] 
public class AdminAuthHub : Hub 
{ 
}

O bien, puede especificar que un centro de conectividad contenga un método que esté disponible para todos los usuarios y un segundo método que solo esté disponible para los usuarios autenticados, como se muestra a continuación.

public class SampleHub : Hub 
{ 
    public void UnrestrictedSend(string message){ . . . } 

    [Authorize] 
    public void AuthenticatedSend(string message){ . . . } 
}

Los siguientes ejemplos abordan diferentes escenarios de autorización:

  • [Authorize]: solo usuarios autentificados
  • [Authorize(Roles = "Admin,Manager")]: solo usuarios autentificados en los roles especificados
  • [Authorize(Users = "user1,user2")]: solo usuarios autentificados con los nombres de usuario especificados
  • [Authorize(RequireOutgoing=false)]: solo los usuarios autentificados pueden invocar el centro de conectividad, pero las llamadas del servidor de vuelta a los clientes no están limitadas por la autorización, como, por ejemplo, cuando solo ciertos usuarios pueden enviar un mensaje pero todos los demás pueden recibirlo. La propiedad RequireOutgoing solo puede aplicarse a todo el centro de conectividad, no a métodos individuales dentro del centro. Cuando RequireOutgoing no está establecido en false, solo se llama desde el servidor a los usuarios que cumplen el requisito de autorización.

Requerir autenticación para todos los centros de conectividad

Puede requerir la autenticación para todos los centros de conectividad y métodos de centro de conectividad de su aplicación llamando al método RequireAuthentication cuando se inicie la aplicación. Puede usar este método cuando tenga varios centros de conectividad y quiera imponer un requisito de autenticación para todos ellos. Con este método, no puede especificar el rol, el usuario o la autorización de salida. Solo puede especificar que el acceso a los métodos del centro de conectividad esté restringido a los usuarios autentificados. Sin embargo, aún puede aplicar el atributo Authorize a los centros de conectividad o a los métodos para especificar requisitos adicionales. Cualquier requisito que especifique en un atributo se aplica además del requisito básico de autenticación.

El siguiente ejemplo muestra un archivo Global.asax que restringe todos los métodos del centro de conectividad a usuarios autentificados.

public partial class Startup {
    public void Configuration(IAppBuilder app) {
        app.MapSignalR();
        GlobalHost.HubPipeline.RequireAuthentication();
    }
}

Si llama al método RequireAuthentication() después de que se haya procesado una solicitud de SignalR, SignalR lanzará una excepción InvalidOperationException. SignalR lanza esta excepción porque no se puede agregar un módulo a HubPipeline después de que la canalización haya sido invocada. El ejemplo anterior muestra la llamada al método RequireAuthentication en el método Configuration que se ejecuta una vez antes de controlar la primera solicitud.

Autorización personalizada

Si necesita personalizar cómo se determina la autorización, puede crear una clase que derive de AuthorizeAttribute y anular el método UserAuthorized. Para cada solicitud, SignalR invoca este método para determinar si el usuario está autorizado para completar la solicitud. En el método invalidado, se proporciona la lógica necesaria para su escenario de autorización. El siguiente ejemplo muestra cómo aplicar la autorización a través de la identidad basada en notificaciones.

[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class AuthorizeClaimsAttribute : AuthorizeAttribute
{
    protected override bool UserAuthorized(System.Security.Principal.IPrincipal user)
    {
        if (user == null)
        {
            throw new ArgumentNullException("user");
        }

        var principal = user as ClaimsPrincipal;

        if (principal != null)
        {
            Claim authenticated = principal.FindFirst(ClaimTypes.Authentication);
            if (authenticated != null && authenticated.Value == "true")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }
}

Pasar información de autenticación a los clientes

Puede que necesite usar información de autenticación en el código que se ejecuta en el cliente. Se pasa la información necesaria al llamar a los métodos en el cliente. Por ejemplo, un método de una aplicación de chat podría pasar como parámetro el nombre de usuario de la persona que publica un mensaje, como se muestra a continuación.

public Task SendChatMessage(string message)
{
    string name;
    var user = Context.User;

    if (user.Identity.IsAuthenticated)
    {
        name = user.Identity.Name;
    }
    else
    {
        name = "anonymous";
    }
    return Clients.All.addMessageToPage(name, message);
}

O bien, puede crear un objeto para representar la información de autenticación y pasarse ese objeto como parámetro, como se muestra a continuación.

public class SampleHub : Hub
{
    public override Task OnConnected()
    {
        return Clients.All.joined(GetAuthInfo());
    }

    protected object GetAuthInfo()
    {
        var user = Context.User;
        return new
        {
            IsAuthenticated = user.Identity.IsAuthenticated,
            IsAdmin = user.IsInRole("Admin"),
            UserName = user.Identity.Name
        };
    }
}

Nunca debe pasarse el id. de conexión de un cliente a otros clientes, ya que un usuario malintencionado podría usarlo para imitar una solicitud de ese cliente.

Opciones de autenticación para clientes .NET

Cuando tenga un cliente .NET, como una aplicación de consola, que interactúe con un centro de conectividad limitado a usuarios autenticados, puede pasar las credenciales de autenticación en una cookie, en el encabezado de conexión o en un certificado. Los ejemplos de esta sección muestran cómo usar esos diferentes métodos para autenticar a un usuario. No son aplicaciones de SignalR plenamente funcionales. Para más información sobre clientes .NET con SignalR, consulte Guía de la API Hubs: Cliente .NET.

Cuando su cliente .NET interactúe con un centro de conectividad que use la Autenticación mediante formularios de ASP.NET, tendrá que establecer manualmente la cookie de autenticación en la conexión. Agregue la cookie a la propiedad CookieContainer en el objeto HubConnection. El siguiente ejemplo muestra una aplicación de consola que recupera una cookie de autenticación de una página web y la agrega a la conexión.

class Program
{
    static void Main(string[] args)
    {
        var connection = new HubConnection("http://www.contoso.com/");
        Cookie returnedCookie;

        Console.Write("Enter user name: ");
        string username = Console.ReadLine();

        Console.Write("Enter password: ");
        string password = Console.ReadLine();

        var authResult = AuthenticateUser(username, password, out returnedCookie);

        if (authResult)
        {
            connection.CookieContainer = new CookieContainer();
            connection.CookieContainer.Add(returnedCookie);
            Console.WriteLine("Welcome " + username);
        }
        else
        {
            Console.WriteLine("Login failed");
        }    
    }

    private static bool AuthenticateUser(string user, string password, out Cookie authCookie)
    {
        var request = WebRequest.Create("https://www.contoso.com/RemoteLogin") as HttpWebRequest;
        request.Method = "POST";
        request.ContentType = "application/x-www-form-urlencoded";
        request.CookieContainer = new CookieContainer();

        var authCredentials = "UserName=" + user + "&Password=" + password;
        byte[] bytes = System.Text.Encoding.UTF8.GetBytes(authCredentials);
        request.ContentLength = bytes.Length;
        using (var requestStream = request.GetRequestStream())
        {
            requestStream.Write(bytes, 0, bytes.Length);
        }

        using (var response = request.GetResponse() as HttpWebResponse)
        {
            authCookie = response.Cookies[FormsAuthentication.FormsCookieName];
        }

        if (authCookie != null)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

La aplicación de consola publica las credenciales en www.contoso.com/RemoteLogin, que podría referirse a una página vacía que contiene el siguiente archivo de código subyacente.

using System;
using System.Web.Security;

namespace SignalRWithConsoleChat
{
    public partial class RemoteLogin : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            string username = Request["UserName"];
            string password = Request["Password"];
            bool result = Membership.ValidateUser(username, password);
            if (result)
            {
                FormsAuthentication.SetAuthCookie(username, false);
            }
        }
    }
}

Autenticación de Windows

Al usar la autenticación de Windows, puede pasar las credenciales del usuario actual usando la propiedad DefaultCredentials. Establezca las credenciales para la conexión con el valor de DefaultCredentials.

class Program
{
    static void Main(string[] args)
    {
        var connection = new HubConnection("http://www.contoso.com/");
        connection.Credentials = CredentialCache.DefaultCredentials;
        connection.Start().Wait();
    }
}

Encabezado de la conexión

Si su aplicación no usa cookies, puede pasar la información del usuario en el encabezado de conexión. Por ejemplo, puede pasar el token en el encabezado de conexión.

class Program
{
    static void Main(string[] args)
    {
        var connection = new HubConnection("http://www.contoso.com/");
        connection.Headers.Add("myauthtoken", /* token data */);
        connection.Start().Wait();
    }
}

Después, en el centro de conectividad, verificaría el token del usuario.

Certificate

Puede pasar un certificado de cliente para verificar al usuario. El certificado se agrega al crear la conexión. El siguiente ejemplo muestra solo cómo agregar un certificado de cliente a la conexión; no muestra la aplicación de consola completa. Usa la clase X509Certificate que proporciona varias formas diferentes de crear el certificado.

class Program
{
    static void Main(string[] args)
    {
        var connection = new HubConnection("http://www.contoso.com/");
        connection.AddClientCertificate(X509Certificate.CreateFromCertFile("MyCert.cer"));
        connection.Start().Wait();
    }
}