SignalR 中心身份验证和授权 (SignalR 1.x)
作者 :Patrick Fletcher, Tom 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 客户端。
Cookie
当 .NET 客户端与使用 ASP.NET Forms 身份验证的中心交互时,需要在连接上手动设置身份验证 Cookie。 将 Cookie 添加到 CookieContainer
HubConnection 对象的 属性。 以下示例演示从网页检索身份验证 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();
}
}