背景
練習用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. ユーザー登録時の設定の変更を行いました。
UserIdとClaimsの組み合わせを保存するため、Asp.NET Core Identityをscaffoldしました。
ただ、AspNetUserClaimsテーブルを作成し、組み合わせを保存するだけならばIdentityをscaffoldする必要はないかもしれませんが、その方法を調べるよりは簡単に思えたため、今回行いました。
scaffoldをした後に、Databaseを見ると、AspNetUserClaimsテーブルが見つかるかと思います。このテーブルに保存されている値を用いて、ユーザーを識別します。
こちらを参考にしました。
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>();
ユーザーを登録する際に、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でメッセージを送信する際に、メールアドレスからユーザーを指定することができるようになりました。
参考文献
- microsoft, “ASP.NET Core SignalR での認証と認可“
- microsoft, “ASP.NET Core プロジェクトでの Identity のスキャフォールディング“
まとめ
今回は、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>();
- microsoft, “.NET Dependency Injection“, 最終更新日 : 01/25/2024
- microsoft, “ServiceLifetime“, 閲覧日 : 16/03/2024
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を用いて設定されます。
メッセージ履歴を表示する機能
相手が後からアプリを開いてもメッセージを表示したい、と考えており、以下のような処理フローを考えました。(今度からシーケンス図にしよう)
通信の接続が確立したとき、サーバー側で3つの処理を行います
- 確立した接続をグループに追加する
- Databaseからグループのチャット履歴を取得する
- チャット履歴を引数として、クライアント側のメッセージを表示するメソッドを呼び出す
メッセージを送信したい場合は、サーバー側で2つの処理を行います
- メッセージをDatabaseへ保存する
- メンバーへメッセージを送信する
上記のような処理の流れがあれば、メッセージ履歴を表示することができると考えています。そのためには、メッセージを保存するテーブル、グループを保存するテーブル、ユーザーを保存するテーブル、そのあたりが必要になってくると考えています。
参考文献
- microsoft, Manage users and groups in SignalR
- microsoft, Authentication and authorization in ASP.NET Core SignalR
コメント