SignalR 中心身份验证和授权

作者 :Patrick FletcherTom FitzMacken

警告

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

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

本主题中使用的软件版本

本主题的早期版本

有关 SignalR 早期版本的信息,请参阅 SignalR 旧版本

问题和评论

请留下反馈,说明你如何喜欢本教程,以及我们可以在页面底部的评论中改进的内容。 如果你有与本教程不直接相关的问题,可以将其发布到 ASP.NET SignalR 论坛StackOverflow.com

概述

本主题包含以下各节:

Authorize 属性

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 属性应用于中心或方法,以指定其他要求。 在属性中指定的任何要求将添加到身份验证的基本要求中。

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

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

如果在处理 SignalR 请求后调用 RequireAuthentication() 方法,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
        };
    }
}

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

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

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

当 .NET 客户端与使用 ASP.NET 窗体身份验证的中心交互时,需要在连接上手动设置身份验证 Cookie。 将 Cookie 添加到 CookieContainerHubConnection 对象的 属性。 以下示例演示一个控制台应用,该应用从网页中检索身份验证 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();
    }
}