MENU

SignalRで特定のユーザーにメッセージを送信する

目次

背景

練習用WebアプリにChat機能を追加したいため、リアルタイムなwebの機能を簡単に追加することができるSignalRについて、調査をしました。

SignalRを用いた簡単なWebアプリを実装し、大まかな処理の流れも把握しましたが、調査の中で新たな疑問が生じました。今回は以下の疑問について調査を行いました。

特定のユーザーにメッセージを送信するにはどうすればよいのか

今回の記事は、Signalrの概要を知っている前提で書いています。Signalrってなんだ?という方は、僕が以前書いたものmicrosoftの公式文書を参考にしつつ、読んでいただけると良いと思います。

また、今回のソースコードをGitHub上で公開しています。良ければ参考にしてください(feature_Userブランチ)。

結果

microsoftの公式ドキュメントに、signalRでユーザー権限と認証を使用する方法を説明したページがあったため、そちらを参考にしました。また、その中でもいくつか方法が紹介されていますが、今回はclaimを利用した方法を用いました。

外側から見た挙動

メールアドレスからメッセージを送信するユーザーを指定する

※動画粗くてすみません。

内部の処理フロー

送信先を指定してメッセージを送信するまでの流れ

特定のユーザーにメッセージを送信

※左上のレイアーから、時系列をみることができます。1. メールアドレスを指定してメッセージを送信、2. Userを識別、3. 全体像1の順番になります。

今回は、送信元のユーザーが送信先のメールアドレスを指定して、メッセージを送信する流れにしました。メールアドレスとユーザーIDの組み合わせはAspNetUserClaimsテーブルに保存しています。

そのため、流れとしては、ユーザーがメールアドレスを指定してメッセージを送信 → Hubがメールアドレスに紐づくユーザーとの接続を取得 → 指定されクライアント側で、メッセージを受信するメソッドを呼び出す。となります。

実装手順

内部の処理フローを行うため、1. Identityの追加、2. ユーザーの特定方法の変更、3. ユーザー登録時の設定の変更を行いました。

STEP
Identityの追加

UserIdとClaimsの組み合わせを保存するため、Asp.NET Core Identityをscaffoldしました。

ただ、AspNetUserClaimsテーブルを作成し、組み合わせを保存するだけならばIdentityをscaffoldする必要はないかもしれませんが、その方法を調べるよりは簡単に思えたため、今回行いました。

scaffoldをした後に、Databaseを見ると、AspNetUserClaimsテーブルが見つかるかと思います。このテーブルに保存されている値を用いて、ユーザーを識別します。

STEP
ユーザーの指定方法の変更

こちらを参考にしました。

SignalRでは、IUserIdProviderを用いてUserの識別を行っております。IUserIdProviderは初期設定でClaimTypes.NameIdentifierを用いた識別を行っており、これをメールアドレスへと変更します。

まず、IUserIdProviderを継承したEmailBasedUserIdProviderを作成しました。

public class EmailBasedUserIdProvider : IUserIdProvider
    {
        public virtual string GetUserId(HubConnectionContext connection)
        {
            return connection.User?.FindFirst(ClaimTypes.Email)?.Value!;
        }
    }

そして、作成したEmailBasedUserIdProviderをServiceCollectionに追加するため、AddSingleton()で追加しました。これらのコードを加えると、Clients.User()で、引数にメールアドレスを渡し、該当するユーザーを受け取ることができます。

// builder.Services.AddSignalR()の後に書く
builder.Services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
STEP
メールアドレスの登録

ユーザーを登録する際に、UserIdとメールアドレスの組み合わせをClaimに保存します。以下のコードをRegister.cshtml.csのOnPostAsync内に追記します。

// Add the email claim and value for this user.
await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, Input.Email));

以上、1 ~ 3までの処理によって、ユーザー登録時にメールアドレスをClaimに挿入、そして、SignalRでメッセージを送信する際に、メールアドレスからユーザーを指定することができるようになりました。

参考文献

まとめ

今回は、signalRで「送信相手を指定してメッセージを送信する方法」を調査しました。今回は、アプリにユーザーを登録する際、ClaimにメールアドレスとUserIdを登録し、メールアドレスを指定することによって、ユーザーを指定することができました。

今後の課題は「セキュリティ面」です。現状では、クライアントからメールアドレスを指定する必要があり、どこかに埋め込んでおく必要があります。暗号化?しとけば、埋め込んでおいていいものか、暗号化ってどうやるのか、そんなことを考えております。そのあたり、アドバイスがあれば、ぜひコメント欄で教えてほしいです。

付録

用語

Service Collection

.NETでは、クラス間の依存関係を管理するため、Dependency Injection software design patternを採用しており、アプリの立ち上げ時にサービスをIService Collectionに追加しています。例えば、ASP.NET MVCを用いて開発したWebアプリケーションの場合、以下のコードを見たことがあるはずです。これは、IService Collectionに、それぞれのServiceを追加しております。

// DbContextの追加
builder.Services.AddDbContext(options => options.UseSqlServer(connectionString));

// Identityの追加
builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores();

// MVCパターンの実装
builder.Services.AddControllersWithViews();

今回の場合、EmailBasedUserIdProviderをServiceCollectionにライフタイムを指定して、追加しております。

// builder.Services.AddSignalR()の後に書く
builder.Services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();

Cookie

Cookieは、web siteが、web browserを介してユーザーのコンピューターに残す小さな情報です。

・・・

CookieはserverレベルでSet-Cookie HTTP headerやJavaScriptのdocument.cookieを用いて、設定することができます。

※拙訳

SignalRの認証方法一覧

Cookie認証

Browser baseのアプリでは、Cookie認証により、現在のユーザーの認証情報が自動的にSignalRの接続へと流れ込みます。Browser clientを使用しているとき、追加で環境設定を行う必要はありません。ユーザーがアプリにログインしている場合、SignalR接続は自動的に認証を継承します。

Bearer token authentication

ClientはCookieを使用する代わりに、access tokenを提供することができます。Serverはtokenを検証(validation)し、tokenを用いて、ユーザー認証を行います。この検証は、接続が確立された際に行われます。接続が消滅するまでの間、serverは自動的にtokenを再検証することはありません。

Built-in JWT認証

サーバー上では、bearer token認証はJWT Bearer middlewareを用いて設定されます。

メッセージ履歴を表示する機能

相手が後からアプリを開いてもメッセージを表示したい、と考えており、以下のような処理フローを考えました。(今度からシーケンス図にしよう)

STEP
チャットルームへの入室

通信の接続が確立したとき、サーバー側で3つの処理を行います

  • 確立した接続をグループに追加する
  • Databaseからグループのチャット履歴を取得する
  • チャット履歴を引数として、クライアント側のメッセージを表示するメソッドを呼び出す
STEP
チャットルームのメンバーへのメッセージの送信

メッセージを送信したい場合は、サーバー側で2つの処理を行います

  • メッセージをDatabaseへ保存する
  • メンバーへメッセージを送信する

上記のような処理の流れがあれば、メッセージ履歴を表示することができると考えています。そのためには、メッセージを保存するテーブル、グループを保存するテーブル、ユーザーを保存するテーブル、そのあたりが必要になってくると考えています。

参考文献

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

CAPTCHA


目次