目的
複数のASP.NET Core Webアプリーションでセッション情報を共有する必要があり、実現方法を調査しました。
本稿ではRedisを用いてセッション情報を共有するまでの手順について記載します。
対象アーキテクチャ
- .NET 6
- ASP.NET Core Webアプリ(Razor Pagesアプリーション)
1.セッションの使用
「ASP.NET Core Webアプリ」のテンプレートで作成したプロジェクトはそのままではセッションが使えません。
Program.cs
に下記コードを追加することでセッションが有効になります。
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(180);
});
app.UseSession();
セッションの詳細な設定については、Microsoftの技術情報のASP.NET Core でのセッションと状態の管理を参考にして下さい。
2.分散キャッシュを用いたセッション管理
先ほどの設定のみでは、セッションデータが各メモリ内に格納され、異なるアプリケーション同士で共有はされません。
これを解消するには分散キャッシュを用います。セッションに分散キャッシュを用いることで、スケールアウトで複数サーバーに配置されたアプリケーション間でもセッション情報を共有できるようになります。
Microsoftの技術情報のASP.NET Core の分散キャッシュではIDistributedCache
の実装となる下記分散キャッシュについて提示されています。
バッキング ストア | 分散キャッシュサービスを追加する実装 |
---|---|
SQL Server | AddDistributedSqlServerCache |
Redis | AddStackExchangeRedisCache |
NCache | AddNCacheDistributedCache |
3.Redisサーバーの準備
今回は分散キャッシュにRedisを選択しました。開発用に使えれば良いのでDockerで立ち上げました。
docker-compose.yaml
version: '3'
services:
redis:
image: "redis:latest"
ports:
- "6379:6379"
4.セッションに用いる分散キャッシュを設定
NugetでMicrosoft.Extensions.Caching.StackExchangRedis
パッケージをインストールします。
これでサービスに分散Redisキャッシュを追加できるようになります。
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "127.0.0.1:6379";
options.InstanceName = "sample";
});
5.データ保護対策
さて、これでセッションが共有できるようになるかと期待したのですが、WebAppA
で設定したセッションを別のWebAppB
で読み込もうとすると以下のような例外が発生するようになりました。
Microsoft.AspNetCore.Session.SessionMiddleware: Warning: Error unprotecting the session cookie.
System.Security.Cryptography.CryptographicException: The payload was invalid. For more information go to http://aka.ms/dataprotectionwarning
at Microsoft.AspNetCore.DataProtection.Cng.CbcAuthenticatedEncryptor.DecryptImpl(Byte* pbCiphertext, UInt32 cbCiphertext, Byte* pbAdditionalAuthenticatedData, UInt32 cbAdditionalAuthenticatedData)
at Microsoft.AspNetCore.DataProtection.Cng.Internal.CngAuthenticatedEncryptorBase.Decrypt(ArraySegment`1 ciphertext, ArraySegment`1 additionalAuthenticatedData)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status)
at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData)
at Microsoft.AspNetCore.Session.CookieProtection.Unprotect(IDataProtector protector, String protectedText, ILogger logger)
ASP.NET Coreではセッションデータを格納する際に暗号化されています。
Microsoftの技術情報のデータ保護の構成のセクションを読むと下記記載が見つかりました。
SetApplicationName
既定では、データ保護システムによって各アプリが、それらのコンテンツルートパスに基づいて互いに分離されます。同じ物理キーリポジトリを共有している場合でも同様です。この分離により、各アプリで互いの保護されたペイロードを把握できなくなります。
保護されたペイロードをアプリ間で共有するには:
- 各アプリで同じ値を使って SetApplicationName を構成します。
- アプリ全体で同じバージョンのデータ保護 API スタックを使います。 各アプリのプロジェクト ファイルで、次のいずれかを実行します。
- Microsoft.AspNetCore.App メタパッケージを使って、同じ共有フレームワーク バージョンを参照します。
- 同じデータ保護パッケージ バージョンを参照します。
今回は同一サーバー上で動作するため、物理キーリポジトリは共有できていると思われますが、コンテンツルートパスが異なります。
下記設定を追加することで、2つのWebアプリでセッション情報が共有されることが確認できました。
builder.Services.AddDataProtection().SetApplicationName("shared");
今回は同一サーバー上の複数アプリケーションでしたので、この設定だけで動作しましたが、複数サーバーで共有する場合は鍵の共有についても検討が必要だと思います。
6.まとめ
- セッションを共有するには分散キャッシュを用います。
- 複数のWebアプリケーションで互いのペイロードを共有するには
SetApplicationName
を用います。
なお、今回の修正を当てたProgram.cs
全文は下記となります。
using Microsoft.AspNetCore.DataProtection;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddDataProtection().SetApplicationName("shared");
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "127.0.0.1:6379";
options.InstanceName = "sample";
});
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(180);
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();
app.Run();
このコードでは接続先やタイムアウトの設定などを、何が設定しているのが分かりやすいよう直接指定しています。
実際に使用する場合はappsettings.json
に設定することを検討してください。