Autenticazione e autorizzazione per SignalR Hubs

di Patrick Fletcher, Tom FitzMacken

Avviso

Questa documentazione non è per la versione più recente di SignalR. Esaminare ASP.NET Core SignalR.

Questo argomento descrive come limitare gli utenti o i ruoli che possono accedere ai metodi hub.

Versioni software usate in questo argomento

Versioni precedenti di questo argomento

Per informazioni sulle versioni precedenti di SignalR, vedere Versioni precedenti di SignalR.

Domande e commenti

Lasciare commenti e suggerimenti su come è piaciuta questa esercitazione e cosa è possibile migliorare nei commenti nella parte inferiore della pagina. Se si hanno domande che non sono direttamente correlate all'esercitazione, è possibile pubblicarle nel forum di ASP.NET SignalR o StackOverflow.com.

Panoramica

In questo argomento sono incluse le sezioni seguenti:

Autorizzare l'attributo

SignalR fornisce l'attributo Authorize per specificare quali utenti o ruoli hanno accesso a un hub o a un metodo. Questo attributo si trova nello spazio dei Microsoft.AspNet.SignalR nomi . L'attributo Authorize viene applicato a un hub o a metodi specifici in un hub. Quando si applica l'attributo Authorize a una classe hub, il requisito di autorizzazione specificato viene applicato a tutti i metodi nell'hub. In questo argomento vengono forniti esempi dei diversi tipi di requisiti di autorizzazione che è possibile applicare. Senza l'attributo Authorize , un client connesso può accedere a qualsiasi metodo pubblico nell'hub.

Se è stato definito un ruolo denominato "Amministrazione" nell'applicazione Web, è possibile specificare che solo gli utenti in tale ruolo possono accedere a un hub con il codice seguente.

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

In alternativa, è possibile specificare che un hub contiene un metodo disponibile per tutti gli utenti e un secondo metodo disponibile solo per gli utenti autenticati, come illustrato di seguito.

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

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

Gli esempi seguenti illustrano diversi scenari di autorizzazione:

  • [Authorize] : solo gli utenti autenticati
  • [Authorize(Roles = "Admin,Manager")] : solo gli utenti autenticati nei ruoli specificati
  • [Authorize(Users = "user1,user2")] : solo gli utenti autenticati con i nomi utente specificati
  • [Authorize(RequireOutgoing=false)] : solo gli utenti autenticati possono richiamare l'hub, ma le chiamate dal server ai client non sono limitate dall'autorizzazione, ad esempio quando solo determinati utenti possono inviare un messaggio, ma tutti gli altri possono ricevere il messaggio. La proprietà RequireOutgoing può essere applicata solo all'intero hub, non ai singoli metodi all'interno dell'hub. Quando RequireOutgoing non è impostato su false, solo gli utenti che soddisfano il requisito di autorizzazione vengono chiamati dal server.

Richiedere l'autenticazione per tutti gli hub

È possibile richiedere l'autenticazione per tutti gli hub e i metodi hub nell'applicazione chiamando il metodo RequireAuthentication all'avvio dell'applicazione. È possibile usare questo metodo quando si hanno più hub e si vuole applicare un requisito di autenticazione per tutti. Con questo metodo non è possibile specificare i requisiti per l'autorizzazione di ruolo, utente o in uscita. È possibile specificare che l'accesso ai metodi hub è limitato agli utenti autenticati. Tuttavia, è comunque possibile applicare l'attributo Authorize agli hub o ai metodi per specificare requisiti aggiuntivi. Qualsiasi requisito specificato in un attributo viene aggiunto al requisito di base dell'autenticazione.

L'esempio seguente mostra un file di avvio che limita tutti i metodi hub agli utenti autenticati.

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

Se si chiama il metodo dopo l'elaborazione RequireAuthentication() di una richiesta SignalR, SignalR genererà un'eccezione InvalidOperationException . SignalR genera questa eccezione perché non è possibile aggiungere un modulo all'hubPipeline dopo che la pipeline è stata richiamata. Nell'esempio precedente viene illustrato come chiamare il RequireAuthentication metodo nel Configuration metodo che viene eseguito una volta prima di gestire la prima richiesta.

Autorizzazione personalizzata

Se è necessario personalizzare la modalità di determinazione dell'autorizzazione, è possibile creare una classe che deriva da AuthorizeAttribute ed eseguire l'override del metodo UserAuthorized . Per ogni richiesta, SignalR richiama questo metodo per determinare se l'utente è autorizzato a completare la richiesta. Nel metodo sottoposto a override si specifica la logica necessaria per lo scenario di autorizzazione. Nell'esempio seguente viene illustrato come applicare l'autorizzazione tramite l'identità basata sulle attestazioni.

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

Passare le informazioni di autenticazione ai client

Potrebbe essere necessario usare le informazioni di autenticazione nel codice eseguito nel client. Le informazioni necessarie vengono passate quando si chiamano i metodi nel client. Ad esempio, un metodo dell'applicazione di chat può passare come parametro il nome utente della persona che pubblica un messaggio, come illustrato di seguito.

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

In alternativa, è possibile creare un oggetto per rappresentare le informazioni di autenticazione e passare tale oggetto come parametro, come illustrato di seguito.

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

Non passare mai l'ID di connessione di un client ad altri client, perché un utente malintenzionato potrebbe usarlo per simulare una richiesta da tale client.

Opzioni di autenticazione per i client .NET

Quando si dispone di un client .NET, ad esempio un'app console, che interagisce con un hub limitato agli utenti autenticati, è possibile passare le credenziali di autenticazione in un cookie, nell'intestazione di connessione o in un certificato. Negli esempi di questa sezione viene illustrato come usare i diversi metodi per l'autenticazione di un utente. Non sono app SignalR completamente funzionali. Per altre informazioni sui client .NET con SignalR, vedere Guida all'API hub - Client .NET.

Quando il client .NET interagisce con un hub che usa ASP.NET Autenticazione basata su form, è necessario impostare manualmente il cookie di autenticazione nella connessione. Aggiungere il cookie alla CookieContainer proprietà nell'oggetto HubConnection . L'esempio seguente mostra un'app console che recupera un cookie di autenticazione da una pagina Web e aggiunge tale cookie alla connessione.

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

L'app console pubblica le credenziali per www.contoso.com/RemoteLogin che potrebbero fare riferimento a una pagina vuota che contiene il file code-behind seguente.

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

Autenticazione di Windows

Quando si usa autenticazione di Windows, è possibile passare le credenziali dell'utente corrente usando la proprietà DefaultCredentials. Le credenziali per la connessione vengono impostate sul valore di DefaultCredentials.

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

Intestazione di connessione

Se l'applicazione non usa i cookie, è possibile passare le informazioni utente nell'intestazione di connessione. Ad esempio, è possibile passare un token nell'intestazione di connessione.

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

Nell'hub verificare quindi il token dell'utente.

Certificato

È possibile passare un certificato client per verificare l'utente. Il certificato viene aggiunto durante la creazione della connessione. Nell'esempio seguente viene illustrato solo come aggiungere un certificato client alla connessione; non visualizza l'app console completa. Usa la classe X509Certificate che offre diversi modi per creare il certificato.

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