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

Патрик Флетчер (Patrick Fletcher), Том ФитцМаккен (Tom FitzMacken)

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

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

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

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

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

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

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

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

Общие сведения

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

Атрибут Authorize

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 взаимодействует с концентратором, использующим ASP.NET проверку подлинности с помощью форм, необходимо вручную задать файл cookie проверки подлинности для подключения. Файл cookie добавляется в CookieContainer свойство объекта HubConnection . В следующем примере показано консольное приложение, которое получает файл 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();
    }
}