次の方法で共有


SignalR ハブの認証と承認 (SignalR 1.x)

作成者: Patrick FletcherTom FitzMacken

警告

このドキュメントは、最新版の SignalR を対象としていません。 ASP.NET Core SignalR に関する記事を参照してください。

このトピックでは、ハブ メソッドにアクセスできるユーザーまたはロールを制限する方法について説明します。

概要

このトピックは、次のセクションで構成されています。

Authorize 属性

SignalR には、ハブまたはメソッドにアクセスできるユーザーまたはロールを指定するための Authorize 属性が用意されています。 この属性は Microsoft.AspNet.SignalR 名前空間にあります。 Authorize 属性は、ハブまたはハブ内の特定のメソッドに適用します。 Authorize 属性をハブ クラスに適用すると、指定された認可要件がハブ内のすべてのメソッドに適用されます。 適用できるさまざまな認可要件を以下に示します。 Authorize 属性がない場合、ハブに接続されているクライアントは、ハブ上のすべてのパブリック メソッドを使用できます。

Web アプリケーションで "Admin" というロールを定義した場合は、次のコードを使って、そのロールのユーザーのみがハブにアクセスできるように指定できます。

[Authorize(Roles = "Admin")] 
public class AdminAuthHub : Hub 
{ 
}

または、以下に示すように、すべてのユーザーが使用できる 1 つのメソッドと、認証されたユーザーのみが使用できる 2 つ目のメソッドを含むハブを指定できます。

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 にモジュールを追加できないためです。 前の例は、最初の要求を処理する前に 1 回実行される Application_Start メソッド内の RequireAuthentication メソッドの呼び出しを示しています。

カスタマイズされた認可

認可の決定方法をカスタマイズする必要がある場合は、AuthorizeAttribute から派生するクラスを作成し、UserAuthorized メソッドをオーバーライドできます。 このメソッドは、要求を完了する権限がユーザーにあるかどうかを判断するために、要求ごとに呼び出されます。 オーバーライドされたメソッドでは、認可シナリオに必要なロジックを指定します。 次の例は、要求ベース ID を通じて認可を適用する方法を示しています。

[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 クライアントの詳細については、Hubs API ガイド - .NET クライアントのページを参照してください。

.NET クライアントが ASP.NET フォーム認証を使うハブと対話する場合、接続に対して認証 Cookie を手動で設定する必要があります。 Cookie を HubConnection オブジェクトの CookieContainer プロパティに追加します。 次の例は、Web ページから認証 Cookie を取得し、その Cookie を接続に追加するコンソール アプリを示しています。 この例の URL https://www.contoso.com/RemoteLogin は、作成する必要がある Web ページを指しています。 このページは、投稿されたユーザー名とパスワードを取得し、その資格情報を使ってユーザーのログインを試みます。

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