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

作者 :帕特里克·弗莱彻汤姆·菲茨马克肯

警告

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

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

概述

本主题包含以下部分:

授权属性

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

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

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