Session に保存される情報と共有範囲
プリザンターの Session は、ログイン状態だけでなく、画面の表示条件、編集中の設定、画面遷移をまたぐメッセージ、認証処理の途中の情報を保持します。同じ保存機構は、アプリ全体の排他ロックやデータ保護キー、SMTP の OAuth トークンにも使われます。
値を読むときは、誰の値かを示す SessionGuid、用途を示す Key、ページを示す Page の組で見ると整理できます。このページでは、保存内容に加えて保存先、読み書きの流れ、保持期間も説明します。
調査範囲
記述は本体ソースの確認によるものです。実環境の Session の採取や、各機能を実行した際の保存内容の測定は行っていません。参照先はコミット 8d29c7bd110c2487ad4b39bd34dafaf0ba66373b に固定しています。
最初に区別する4つの情報
| 名前 | 内容 | 存続する範囲 |
|---|---|---|
Context.SessionGuid | Cookie から復元する独自のセッション識別子 | 同じセッション Cookie を使うリクエスト |
Context.SessionData | その識別子と現在のページに対応する保存値を読み込んだ辞書 | 辞書自体は現在の Context。元の値は保存先に残る |
Context.UserSessionData | @{ユーザー ID} を識別子として読み込んだ保存値の辞書 | 元の値はユーザー単位でリクエストをまたぐ |
サーバースクリプトの context.UserData | Context が生成する ExpandoObject をスクリプトへ渡すもの | 同じ Context 内。Session への永続化処理はない |
根拠は Context.cs#L46-L57、Context.cs#L331-L394、Context.cs#L482-L518、Context.cs#L629-L640、ServerScriptModel.cs#L97-L106 です。サーバースクリプトの context.UserData を、Sessions API の保存値と同じものとして扱わないでください。
ASP.NET Core の HttpContext.Session も設定されていますが、本体の Context.SetSessionData() を呼ぶ箇所は確認したソース内にありません。以下で扱う値は SessionUtilities を使う独自の保存機構の値です(Startup.cs#L128-L135、Context.cs#L1295-L1307)。
保存先と設定
App_Data/Parameters/Session.json の既定値は次のとおりです(Session.json)。
{
"RetentionPeriod": 1440,
"UseKeyValueStore": false
}RetentionPeriod の単位は分で、既定値は 24 時間です。ただし RDB では期限を過ぎた瞬間のアクセスを拒否する設定ではなく、古い行を削除する際の基準です。Redis では書き込み時の TTL に使います。
| 設定・値の種類 | 書き込み先 | 格納方法 |
|---|---|---|
UseKeyValueStore: false | RDB の Sessions テーブル | SessionGuid・Key・Page の組を主キーとする行 |
UseKeyValueStore: true かつ内部値 | Redis | GUID をキーとするハッシュ。ページ単位のフィールド名は {Key}_{Page} |
UseKeyValueStore: true かつ UserArea の値 | RDB の Sessions テーブル | Redis 利用時も API 用の任意値は RDB に分岐 |
Value は文字列で、複雑なオブジェクトは各機能が JSON などへ変換して保存します。RDB には一回読み込み用の ReadOnce と API 領域を示す UserArea もあります(SessionUtilities.cs#L141-L192、Sessions_Key.json、Sessions_Page.json)。
読み込みのたびに RDB と Redis を統合する実装ではありません。Get() は UseKeyValueStore で読み込み先を決めるため、Redis 有効時の API 用任意値は書き込み先との不一致が生じます(SessionUtilities.cs#L31-L86)。
保存範囲はブラウザ、ユーザー、アプリ共有に分かれる
図を読み込み中…
SessionGuid | 代表的な値 | 読み書きする主体 |
|---|---|---|
Cookie Pleasanter_SessionGuid の GUID | View、SiteSettings、Message など | その Cookie を送るリクエスト |
@{ユーザー ID} | ユーザー単位の View、User_{任意キー} | 同じユーザーとして解決されたリクエスト |
| 認証チケットごとに新規生成する GUID | AuthenticationTicket | Cookie 認証のチケットストア |
SessionExclusive | TableExclusive_SiteId={サイト ID} | サイト処理の排他制御 |
@AspNetCoreDataProtectionKeys | 名前をキーとする XML | 既定のデータ保護キーリポジトリ |
SmtpOAuthToken | OAuthToken:{クライアント ID} | SMTP の OAuth 認証処理 |
根拠は Views.cs#L9-L224、SessionUtilities.cs#L323-L390、AuthenticationTicketStore.cs#L14-L77、SessionExclusive.cs#L11-L103、AspNetCoreKeyManagementXmlRepository.cs#L14-L65、Smtp.cs#L222-L295 です。
ここでいうブラウザ単位は Cookie による区分です。タブを識別するキーはなく、同じ Cookie と同じ Page で保存された値を各タブが読みます。一方、@{ユーザー ID} の値は Cookie の GUID が異なる場合も同じ保存先を使います。
Page はタブ番号ではなく画面の識別子
page: true の書き込みでは context.Page を保存します。items のページは items/{サイト ID}、ゴミ箱はその末尾に /trashbox が付きます。page: false の値は空文字のページに保存されます(Context.cs#L443-L471、SessionUtilities.cs#L141-L192)。
RDB の読み込み対象は Page が空文字か現在の context.Page と一致する行です。Context.SessionData には全サイトの値を一括で読み込むわけではありません(SessionUtilities.cs#L31-L86)。
ログインと認証処理で扱う情報
| キー | 内容 | 保存範囲・読み手 |
|---|---|---|
AuthenticationTicket | シリアライズした認証チケットを Base64 にした文字列 | チケット専用 GUID。AuthenticationTicketStore が復元 |
SwitchLoginId | 切り替え先のログイン ID | Cookie の GUID、ページ共通。特権ユーザーの切り替え処理 |
Passkeys_fido2.attestationOptions | パスキー登録要求のオプションの JSON | Cookie の GUID、ページ共通。登録応答の検証処理 |
Passkeys_fido2.assertionOptions | パスキーログイン要求のオプションの JSON | Cookie の GUID、ページ共通。認証応答の検証処理 |
認証チケットは通常の画面用 GUID と別に保存する
AuthenticationTicketStore.StoreAsync() は GUID を新規生成してチケットを保存し、その GUID を Cookie 認証基盤に返します。更新・取得・削除も、このチケット用 GUID で行います(AuthenticationTicketStore.cs#L14-L77、Startup.cs#L150-L193)。
フォーム認証のサインイン処理では ClaimTypes.Name にログイン名を入れた ClaimsPrincipal と認証プロパティを作ります。ユーザー情報の解決は別途 Context が行い、Cookie 認証のログイン名や、指定された API キーから Users の情報を取得します。認証チケットをユーザーマスターの全項目のコピーとして理解するのは誤りです(Context.cs#L1327-L1337、Context.cs#L482-L518)。
AuthenticationTicket の Base64 化はチケットの保存形式です。独自の GUID を暗号化して保存する Pleasanter_SessionGuid の Cookie と、役割も形式も異なります。
切り替え先とパスキーの処理途中の情報
SwitchLoginId はユーザー切り替え時に書かれ、切り替えの解除で削除されます。Context のユーザー解決でも参照されます(UserUtilities.cs#L5217-L5291、Context.cs#L482-L518)。
パスキーでは、登録・認証の要求を作る際にオプションを保存し、ブラウザからの応答を受けた処理が取り出して削除します。登録済みパスキーの一覧は別途 PasskeyCollection から取得しています。ここで保存するのは、要求と応答を結び付けるための処理途中の情報です(PasskeysController.cs#L26-L166、PasskeysController.cs#L223-L308、PasskeyUtilities.cs#L250-L292)。
一覧・カレンダーなどの表示状態
| キー | 保存内容 | 範囲 |
|---|---|---|
View | フィルター、ソート、検索語、表示項目、各表示方式の条件などを選別した JSON | ページ単位。保存種別に応じて Cookie の GUID または @{ユーザー ID} |
View_{テーブル名} | リンク先テーブル用のビューの JSON | ページ単位。通常のビューと同様に保存種別で分岐 |
ViewMode | サイト ID と表示アクションを対応付けた辞書の JSON | Cookie の GUID、ページ共通 |
Views.GetBySession() は保存されたビューを復元し、フォームの変更を反映して保存します。「ユーザ」の場合は UserSessionData を読み、保存先を @{ユーザー ID} に切り替えます。「保存しない」の場合は書き込みを抑止します。MCP からの処理でも保存を抑止します(Views.cs#L9-L224)。
| ビューの保存種別 | 保存先と共有範囲 |
|---|---|
| セッション | Cookie の GUID に対応するページ単位の値。同じ Cookie を使う要求で復元 |
| ユーザ | @{ユーザー ID} に対応するページ単位の値。別の Cookie の要求でも同じユーザーなら復元 |
| 保存しない | ビュー変更を Session に書き込まない |
View に入るものはフィルターだけではない
保存するのは View 全体をそのまま JSON にしたものではなく、View.GetRecordingData() で作った記録用のオブジェクトです。空の値や既定値との一致など、項目ごとに保存条件があります(View.cs#L1458-L1885)。
| 分類 | 保存対象となるプロパティの例 |
|---|---|
| 基本の表示 | Id、Name、DefaultMode、GridColumns |
| 絞り込み・検索 | ColumnFilterHash、ColumnFilterSearchTypes、ColumnFilterNegatives、Search、Own、Incomplete |
| ソート | ColumnSorterHash |
| パネルの表示 | FiltersDisplayType、FiltersReduced、AggregationsReduced |
| カレンダー | CalendarDate、CalendarStart、CalendarEnd、CalendarViewType、CalendarGroupBy |
| ダッシュボード | DashboardPartLayoutHash、パーツ別のカレンダー状態の各ハッシュ |
| クロス集計 | CrosstabGroupByX、CrosstabGroupByY、CrosstabAggregateType、CrosstabValue |
| ガントチャート | GanttGroupBy、GanttSortBy、GanttPeriod、GanttStartDate |
| 時系列・分析 | TimeSeriesGroupBy、TimeSeriesAggregateType、TimeSeriesChartType、AnalyPartSettings |
| カンバン | KambanGroupByX、KambanGroupByY、KambanValue、KambanColumns |
ViewMode はこれとは別のキーです。サイトごとの context.Action を一つの辞書に保存します。読み込み時は既定ビューの DefaultMode があればそちらを優先し、保存値がなければ index を使います(ViewModes.cs#L9-L48)。
管理画面で編集中の情報
設定画面の操作では、複雑な設定を Session に置いて次の要求へ引き継ぎます。たとえばサイト設定変更時は SiteSettings.RecordingJson() を SiteSettings キーに保存し、フォームにその項目がない場合は保存値から復元します(SiteModel.cs#L2512-L2525、SiteModel.cs#L1937-L1941)。
| キー | 内容 | 保存範囲・定義 |
|---|---|---|
SiteSettings | サイト設定の記録用 JSON | Cookie の GUID、ページ単位。SiteModel.cs#L404-L548 |
MonitorChangesColumns | 変更監視の項目リスト | 同上。SiteModel.cs#L404-L548 |
TitleColumns | タイトル項目のリスト | 同上。SiteModel.cs#L404-L548 |
Export | エクスポート設定 | 同上。SiteModel.cs#L404-L548 |
DisableSiteCreatorPermission | サイト作成者の権限に関する設定値 | 同上。SiteModel.cs#L404-L548 |
UserSettings | ユーザー設定の JSON | Cookie の GUID、ページ単位。UserModel.cs#L849-L906 |
MailAddresses | メールアドレスのリスト | 同上。UserModel.cs#L849-L906 |
TenantSettings | テナント設定 | Cookie の GUID、ページ単位。TenantModel.cs#L422-L450 |
BinarySettings | バイナリの設定 | Cookie の GUID、ページ単位。BinaryModel.cs#L220-L248 |
これらの読み取りメソッドは、Session に値がなければモデルのプロパティを使います。Session の値は編集中の状態を引き継ぐ役割があり、レコードに確定保存された設定と区別して読む必要があります。
メッセージ、表示補助、一時ファイル
| キー | 内容・用途 | 根拠 |
|---|---|---|
StartTime、LastAccessTime | セッション開始時刻と要求間隔の計算に使う時刻。文字列として保存 | SessionUtilities.cs#L198-L214、Context.cs#L710-L720 |
Message | 作成・削除・コピーなどの後、次の画面に出すメッセージの JSON。ReadOnce を指定 | SessionUtilities.cs#L236-L243 |
Responsive | レスポンシブ表示設定の真偽値の文字列 | ResourcesController.cs#L39-L49、Context.cs#L629-L640 |
Language | 未ログイン時の言語決定で使う言語コード。クエリ文字列で指定された値を保存 | Context.cs#L861-L903 |
ClosedAnnouncement:{お知らせ ID} | 閉じたお知らせを示す文字列 true | UserUtilities.cs#L5348-L5360、HtmlHeaders.cs#L108-L120 |
ExceptionSiteId | エラー画面からの案内に使う、管理可能なサイト ID | HandleErrorExAttribute.cs#L37-L41、HtmlTemplates.cs#L330-L358 |
TempFile_{GUID} | 一時ファイルのダウンロードを許可する印。値は空文字 | BinaryUtilities.cs#L700-L739 |
| ファイル名・対象 ID・GUID から生成した SHA-512 ハッシュ | 添付 API の分割アップロードを継続するための一時 GUID | BinariesController.cs#L115-L122、BinariesController.cs#L340-L358 |
値が空でも意味がある
一時ファイルの許可は値の内容でなく、TempFile_{GUID} というキーの存在で判定します。ファイル本体を Session に格納する処理ではありません。分割アップロードでも、Session に置く値は一時 GUID です。
ReadOnce は RDB と Redis で処理が異なる
RDB の Get() は対象の値を SELECT し、ApiRequestBody == null の場合に、その GUID の ReadOnce 行を削除して取得結果を辞書で返します。削除の条件に Page はありません。画面に表示された瞬間を検出して消す処理ではありません(SessionUtilities.cs#L31-L86)。
Redis では {GUID}_readOnce を別ハッシュとして読み、ハッシュごと削除します。こちらには RDB と同じ API 本文による削除抑止がありません。
Sessions API の任意値
Sessions API の値には User_ という接頭辞が付き、UserArea: true として保存されます。たとえば SessionKey が MyApp.Preference なら、内部のキーは User_MyApp.Preference です(SessionUtilities.cs#L283-L317)。
| 設定 | SessionGuid | 共有範囲 |
|---|---|---|
SavePerUser: false | context.SessionGuid | 同じ Session Cookie の要求 |
SavePerUser: true | @{context.UserId} | 同じユーザーの要求 |
API の Get は GetUserArea() を呼び、要求されたキーに必ず User_ を付けて検索します。SessionKey に AuthenticationTicket や SiteSettings を指定しても、同名の内部キーを読む処理にはなりません。Sessions API は内部 Session 全体を閲覧する API ではありません(SessionUtilities.cs#L323-L390)。
また、RDB では Context が UserArea の値を含めて読み込むのは Controller == "sessions" のときです。通常画面の SessionData には API 用の値を含めません(Context.cs#L629-L640、Context.cs#L482-L518)。
操作は /api/sessions/set、/api/sessions/get、/api/sessions/delete への POST です。認証されたユーザーの範囲で処理し、保存時は空でない SessionKey と SessionValue、取得・削除時は空でない SessionKey を要求します。保存・取得・削除のいずれも、同じキーと SavePerUser の指定を使います。取得対象が存在しない場合は NotFound を返します(SessionUtilities.cs#L323-L390、SessionUtilities.cs#L396-L429、SessionsController.cs#L18-L67)。
上のパスはルート配置の場合です。サブディレクトリ配置ではアプリケーションの公開パスを先頭に付けます。
アプリ全体で共有する情報
サイト処理の排他ロック
SessionExclusive という識別子の下に、サイト ID、ユーザー ID、呼び出し元などのコメント、更新時刻を持つロック情報を JSON で保存します。テーブル単位のキーは TableExclusive_SiteId={サイト ID} です。
ロック取得時は更新時刻から 120 秒を超えた値を失効したものとして扱い、更新処理には 30 秒の更新間隔の判定があります。これらはロックの鮮度の判定で、Session.json のデータ保持期間とは別です(SessionExclusive.cs#L11-L103)。
データ保護キー
既定のキーリポジトリでは @AspNetCoreDataProtectionKeys に名前をキーとする XML を保存します。Cookie などを保護・復号するためのアプリ共有情報です。既定構成では XML 内のキーを暗号化する AspNetCoreKeyManagementXmlEncryptor も設定されます(Startup.cs#L273-L311、AspNetCoreKeyManagementXmlRepository.cs#L14-L65、AspNetCoreKeyManagementXmlEncryptor.cs#L9-L21)。
Security.json のデータ保護設定によって Azure Blob Storage や別指定の Redis を使う経路もあります。したがって、この識別子の値が常に存在するとは限りません。
SMTP の OAuth アクセストークン
SmtpOAuthToken の識別子と OAuthToken:{クライアント ID} のキーで、Token と ExpiresAtUtc を持つ JSON を保存します。トークン有効期限が現在時刻と OAuthTokenRefreshBufferTime による余裕時間を超えるときだけ再利用し、それ以外は再取得します(Smtp.cs#L222-L295)。
ブラウザの表示状態とは独立したメール送信処理のキャッシュです。Session の保存値を調査結果として共有するときは、トークンや認証チケット、メールアドレス、保護キーの XML を本文やログへ転載しないでください。
データの寿命を一つの期限で理解しない
| 対象 | 寿命を決める処理 |
|---|---|
| 通常の RDB の保存値 | UpdatedTime と RetentionPeriod による日次削除。GUID 全体ではなく行ごとの判定 |
@ で始まる RDB の保存値 | 通常の日次削除の対象外。ユーザー単位の値や保護キーが該当 |
| Redis のハッシュ | 書き込み時に RetentionPeriod 分の TTL を設定。@ で始まる識別子にも適用 |
Message | 上記に加えて ReadOnce の読み込み時削除 |
| パスキーの要求オプション | 登録・認証の応答処理で明示的に削除 |
| 認証チケット | Cookie 認証基盤の更新・削除と保存先の保持処理 |
| 排他ロック | 更新時刻による独自の鮮度判定と処理終了時の削除 |
| SMTP トークン | 保存先の保持処理に加えて ExpiresAtUtc の有効性判定 |
根拠は SessionUtilities.cs#L432-L452、SessionUtilities.cs#L141-L192 と各機能の参照先です。@ の削除除外は RDB の条件であり、Redis で無期限になるという意味ではありません。
ログイン時の通常 GUID のローテーションは、既存の値を新しい GUID へ移す処理です。ユーザー単位やアプリ共有の識別子を一緒に振り直す処理ではありません(Context.cs#L1445-L1521)。
調査で見る順番
- 症状に関係するキーを上の一覧から選びます。表示条件なら
ViewとViewMode、設定画面ならSiteSettingsなどが候補です。 SessionGuidが Cookie の GUID、@{ユーザー ID}、機能専用の識別子のどれかを確認します。- ページ単位なら
Pageが一致するか、ビューなら保存種別がどれかを確認します。 UseKeyValueStoreとUserAreaを確認し、実際の保存先と読み込み経路を調べます。ReadOnce、明示削除、保持期間、機能独自の有効性判定を分けて確認します。
Redis の設定時もユーザー領域への書き込みは RDB へ分岐しますが、Get() は Redis を読みます。また、Redis のフィールド名は _ でキーとページを分割します。キー自体に _ がある Passkeys_... や TempFile_... は、後半がページとして判定されて読み込みから除外されたり、読み込まれても前半だけのキーになったりします。RDB と同じキーが復元されると考えず、保存時のフィールド名と復元時の分割を照合してください(SessionUtilities.cs#L31-L86)。