SignalR 中心身份验证和授权 (SignalR 1.x)

作者 :Patrick FletcherTom FitzMacken

警告

本文档不适用于最新版本的 SignalR。 查看 ASP.NET Core SignalR

本主题介绍如何限制哪些用户或角色可以访问中心方法。

概述

本主题包含以下各节:

授权属性

SignalR 提供 Authorize 属性,用于指定哪些用户或角色有权访问中心或方法。 此属性位于 命名空间中 Microsoft.AspNet.SignalR 。 将 Authorize 属性应用于中心或中心中的特定方法。 将 属性应用于 Authorize 中心类时,指定的授权要求将应用于中心中的所有方法。 可以应用的不同类型的授权要求如下所示。 Authorize如果没有 属性,中心上的所有公共方法都可供连接到中心的客户端使用。

如果在 Web 应用程序中定义了名为“管理员”的角色,则可以使用以下代码指定只有该角色中的用户才能访问中心。

[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 属性应用于中心或方法以指定其他要求。 除了身份验证的基本要求外,还会应用在属性中指定的任何要求。

以下示例显示了一个 Global.asax 文件,该文件将所有中心方法限制为经过身份验证的用户。

public class Global : HttpApplication
{
    void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapHubs();
        GlobalHost.HubPipeline.RequireAuthentication();
    }
}

如果在处理 SignalR 请求后调用 RequireAuthentication() 方法,SignalR 将引发 InvalidOperationException 异常。 引发此异常的原因是,在调用管道后,无法将模块添加到 HubPipeline。 前面的示例演示了在处理第一个请求之前执行一次的方法中Application_Start调用 RequireAuthentication 方法。

自定义授权

如果需要自定义如何确定授权,可以创建派生自 AuthorizeAttribute 的类,并重写 UserAuthorized 方法。 为每个请求调用此方法,以确定用户是否有权完成请求。 在重写的 方法中,为授权方案提供必要的逻辑。 以下示例演示如何通过基于声明的标识强制执行授权。

[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 = (ClaimsPrincipal)user;

        if (principal != null)
        {
            Claim authenticated = principal.FindFirst(ClaimTypes.Authentication);
            return authenticated.Value == "true" ? true : 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
        };
    }
}

切勿将一个客户端的连接 ID 传递给其他客户端,因为恶意用户可能会使用它来模拟来自该客户端的请求。

.NET 客户端的身份验证选项

当你有一个 .NET 客户端(例如控制台应用)与仅限经过身份验证的用户的中心交互时,可以在 Cookie、连接标头或证书中传递身份验证凭据。 本部分中的示例演示如何使用这些不同的方法对用户进行身份验证。 它们不是功能齐全的 SignalR 应用。 有关使用 SignalR 的 .NET 客户端的详细信息,请参阅 中心 API 指南 - .NET 客户端

当 .NET 客户端与使用 ASP.NET Forms 身份验证的中心交互时,需要在连接上手动设置身份验证 Cookie。 将 Cookie 添加到 CookieContainerHubConnection 对象的 属性。 以下示例演示从网页检索身份验证 Cookie 并将该 Cookie 添加到连接的控制台应用。 示例中的 URL https://www.contoso.com/RemoteLogin 指向需要创建的网页。 该页面将检索已发布的用户名和密码,并尝试使用凭据登录用户。

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