Проверка подлинности и авторизация для Центров SignalR

Патрик Флетчер, Том ФицМАккен

Предупреждение

Эта документация не подходит для последней версии SignalR. Взгляните на ASP.NET Core SignalR.

В этом разделе описывается, как ограничить, какие пользователи или роли могут получить доступ к методам концентратора.

Версии программного обеспечения, используемые в этом разделе

Предыдущие версии этого раздела

Сведения о более ранних версиях SignalR см. в разделе SignalR более ранних версий.

Вопросы и комментарии

Оставьте отзыв о том, как вам понравилось это руководство и что, по вашему мнению, мы можем улучшить в комментариях в нижней части страницы. Если у вас есть вопросы, которые не связаны напрямую с руководством, их можно опубликовать на форуме ASP.NET SignalR или StackOverflow.com.

Обзор

Этот раздел состоит из следующих подразделов.

Атрибут авторизации

SignalR предоставляет атрибут Authorize , чтобы указать, какие пользователи или роли имеют доступ к концентратору или методу. Этот атрибут расположен в Microsoft.AspNet.SignalR пространстве имен. Вы применяете атрибут Authorize к концентратору или к отдельным методам внутри концентратора. При применении атрибута Authorize к классу концентратора указанное требование авторизации применяется ко всем методам в концентраторе. В этом разделе приведены примеры различных типов требований авторизации, которые можно применить. Без атрибута Authorize подключенный клиент имеет доступ к любому общедоступному методу на хабе.

Если вы определили роль с именем "Администратор" в веб-приложении, можно указать, что только пользователи в этой роли могут получить доступ к концентратору со следующим кодом.

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

Кроме того, можно указать, что концентратор содержит один метод, доступный всем пользователям, и второй метод, доступный только для прошедших проверку подлинности пользователей, как показано ниже.

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

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

В следующих примерах рассматриваются различные сценарии авторизации:

  • [Authorize] — только прошедшие проверку подлинности пользователи
  • [Authorize(Roles = "Admin,Manager")] — только прошедшие проверку подлинности пользователи в указанных ролях
  • [Authorize(Users = "user1,user2")] — только прошедшие проверку подлинности пользователи с указанными именами пользователей
  • [Authorize(RequireOutgoing=false)] — только прошедшие проверку подлинности пользователи могут вызывать концентратор, но вызовы с сервера обратно к клиентам не ограничиваются авторизацией, например, когда только определенные пользователи могут отправлять сообщение, но все остальные могут получать сообщение. Свойство RequireOutgoing может применяться только ко всему концентратору, а не к отдельным методам в концентраторе. Если параметр RequireOutgoing не установлен в false, с сервера вызываются только пользователи, соответствующие требованию авторизации.

Требовать проверку подлинности для всех центров

Вы можете требовать проверку подлинности для всех узлов и методов узлов в приложении, вызвав метод RequireAuthentication при запуске приложения. Этот метод можно использовать, если у вас несколько центров и требуется применить требование проверки подлинности для всех них. С помощью этого метода нельзя указать требования к роли, пользователю или исходящей авторизации. Вы можете указать, что доступ к методам концентратора ограничен исключительно пользователями, прошедшими проверку подлинности. Однако атрибут Authorize все еще можно применить к хабам или методам, чтобы указать дополнительные требования. Любое требование, указанное в атрибуте, добавляется к базовому требованию проверки подлинности.

В следующем примере показан файл запуска, который ограничивает все методы концентратора для прошедших проверку подлинности пользователей.

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

Если вы вызовете метод RequireAuthentication() после обработки запроса SignalR, SignalR выбросит исключение InvalidOperationException. SignalR создает это исключение, так как невозможно добавить модуль в HubPipeline после вызова конвейера. В предыдущем примере показано, как вызвать RequireAuthentication метод в методе Configuration , который выполняется один раз перед обработкой первого запроса.

Настраиваемая авторизация

Если необходимо настроить способ определения авторизации, можно создать класс, производный от AuthorizeAttribute метода UserAuthorized , и переопределить его. Для каждого запроса SignalR вызывает этот метод, чтобы определить, авторизован ли пользователь для выполнения запроса. В переопределенном методе вы предоставляете необходимую логику для сценария авторизации. В следующем примере показано, как применить авторизацию посредством удостоверения, основанного на утверждениях.

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

Передача сведений о проверке подлинности клиентам

Возможно, потребуется использовать сведения о проверке подлинности в коде, который выполняется на клиенте. При вызове методов на клиенте передаётся требуемая информация. Например, метод приложения чата может принимать в качестве параметра пользовательское имя человека, публикующего сообщение, как показано ниже.

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

Кроме того, можно создать объект для представления сведений проверки подлинности и передать этот объект в качестве параметра, как показано ниже.

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

Никогда не следует передавать идентификатор подключения одного клиента другим клиентам, так как злоумышленник может использовать его для имитации запроса от этого клиента.

Параметры проверки подлинности для клиентов .NET

Если у вас есть клиент .NET, например консольное приложение, которое взаимодействует с концентратором, который ограничен проверкой подлинности пользователей, вы можете передать учетные данные проверки подлинности в файле cookie, заголовке подключения или сертификате. В примерах этого раздела показано, как использовать эти различные методы для проверки подлинности пользователя. Они не являются полностью функциональными приложениями SignalR. Дополнительные сведения о клиентах .NET с SignalR см. в руководстве по API Центров — клиент .NET.

Когда клиент .NET взаимодействует с хабом, использующим Forms аутентификацию ASP.NET, необходимо вручную задать файл cookie проверки подлинности для соединения. Вы добавляете cookie в CookieContainer свойство объекта HubConnection. В следующем примере показано консольное приложение, которое извлекает файл cookie проверки подлинности с веб-страницы и добавляет этот файл cookie в подключение.

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

Консольное приложение отправляет учетные данные в www.contoso.com/RemoteLogin, которая может ссылаться на пустую страницу, содержащую следующий файл кода.

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

аутентификация Windows

При использовании проверки подлинности Windows можно передать учетные данные текущего пользователя с помощью свойства DefaultCredentials . Вы задаете учетные данные для подключения к параметру DefaultCredentials.

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

Заголовок соединения

Если приложение не использует файлы cookie, вы можете передать сведения о пользователе в заголовке подключения. Например, токен можно передать в заголовке подключения.

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

Затем в центре следует проверить токен пользователя.

Сертификат

Вы можете передать сертификат клиента для проверки пользователя. Сертификат добавляется при создании подключения. В следующем примере показано, как добавить сертификат клиента в подключение; Он не отображает полное консольное приложение. Он использует класс X509Certificate , который предоставляет несколько различных способов создания сертификата.

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