Tomcat のセッションと Cookie の詳細な説明

Tomcat のセッションと Cookie の詳細な説明

序文

HTTP はステートレスな通信プロトコルです。各リクエストは互いに独立しており、サーバーは以前のリクエストを識別できません。 Web アプリケーションの場合、そのアクティビティはすべて、ユーザーのログインなどの特定の状態に依存します。現時点では、HTTP を使用するには、ログイン要求後の後続の要求に対してログイン情報を提供できる必要があります。この記事は、最初に公開アカウント Dunwu Source Code で公開されました。

解決策は、サーバーからブラウザに返される Cookie を使用することです。ブラウザは Cookie データをキャッシュし、リクエストごとにサーバーに送信します。クッキーはリクエスト内でプレーンテキストで送信され、サイズは 4KB に制限されています。明らかに、すべての状態データをブラウザに保存するのは信頼性に欠けます。主流のアプローチは次のとおりです。

  1. ブラウザが最初のリクエストを行うと、サーバーはユーザーに一意の識別子を割り当て、それが返されてブラウザの Cookie に保存されます。
  2. サーバーはグローバルな要求ステータス ライブラリを維持し、生成された一意の識別子を使用して各要求のステータス情報を関連付けます。
  3. ブラウザからの後続のリクエストでは、前のリクエストのステータス情報を取得するために、一意の識別子がサーバーに送信されます。

管理を容易にするために、サーバーはプロセス全体をセッションと呼び、それを Session クラスに抽象化します。このクラスは、ユーザーに関する情報やステータスを識別して保存するために使用されます。
次に、セッション識別子の解析と生成、セッションの作成、破棄、および永続化を通じて、バージョン 6.0.53 を使用した Tomcat のソース コード実装を分析します。

1. セッション識別子を解決する

Cookie は最も一般的に使用されるセッション トラッキング メカニズムです。Tomcat を含むすべてのサーブレット コンテナーがこれをサポートしています。Tomcat では、セッション ID を保存する Cookie の標準名は JSESSIONID です。

ブラウザが Cookie をサポートしていない場合は、次の方法で識別子を記録することもできます。

  • URL 書き換え: /path;JSESSIONID=xxx などのパス パラメータとして URL に含まれます。
  • URL リクエスト パラメータ: ページ上のすべてのリンクに、セッションの一意の識別子をクエリ パラメータとして追加します (例: /path?JSESSIONID=xxx)。
  • フォームの隠しフィールド: 隠しフィールドはフォーム内で一意の値を保存するために使用され、フォームとともにサーバーに送信されます。

Tomcat は、URL 書き換えパスと Cookie から JSESSIONID を抽出する機能を実装します。ソース コードを分析する前に、まず、Cookie を設定するための応答と Cookie を含むリクエストのヘッダー フィールドの重要な情報を確認します。

// クッキーを設定
HTTP/1.1 200 OK
サーバー: Apache-Coyote/1.1
Cookie の設定: JSESSIONID=56AE5B92C272EA4F5E0FBFEFE6936C91; パス=/examples
日付: 2019年5月12日(日)01:40:35 GMT

// クッキーを送信
GET /examples/servlets/servlet/SessionExample HTTP/1.1
ホスト: localhost:8080
クッキー: JSESSIONID=56AE5B92C272EA4F5E0FBFEFE6936C91

1.1 URLからのパスの書き換え

セッション ID パス パラメータを含む URL は次のとおりです。

http://localhost:8080/examples/SessionExample;JSESSIONID=1234;n=v/?x=x

簡単に言えば、一致するセミコロンと最後のスラッシュの間の JSESSIONID を見つけることです。これは確かにその通りですが、Tomcat はバイトで動作します。コア コードは CoyoteAdapter.parsePathParameters() メソッドにありますが、ここでは公開されていません。

1.2 Cookieヘッダーから

Cookie 解析をトリガーするメソッド呼び出しは次のとおりです。

CoyoteAdapter.service(リクエスト、レスポンス)
└─CoyoteAdapter.postParseRequest(リクエスト、リクエスト、レスポンス、レスポンス)
 └─CoyoteAdapter.parseSessionCookiesId(リクエスト、リクエスト)
 └─Cookies.getCookieCount()
 └─Cookies.processCookies(MimeHeaders)
 └─Cookies.processCookieHeader(byte[], int, int)

この processCookieHeader はバイトに対して動作し、解析は直感的ではないようです。Tomcat には、理解を助けるために文字列解析を使用する非推奨としてマークされたメソッドもあります。コードは次のとおりです。

プライベートvoid processCookieHeader(String cookieString){
 // 複数の Cookie 値はカンマで区切られます StringTokenizer tok = new StringTokenizer(cookieString, ";", false);
 (tok.hasMoreTokens()) の間 {
  文字列トークン = tok.nextToken();
  // 等号の位置を取得します int i = token.indexOf("=");
  もし (i > -1) {
   // 名前と値を取得し、スペースを削除します。String name = token.substring(0, i).trim();
   文字列値 = token.substring(i+1, token.length()).trim();
   // RFC 2109 およびバグにより両端の二重引用符が削除されました"
   値 = stripQuote( 値 );
   // 内部 Cookie キャッシュ プールから ServerCookie オブジェクトを取得します。 ServerCookie cookie = addCookie();
   // 名前と値を設定する
   cookie.getName().setString(名前);
   cookie.getValue().setString(値);
  } それ以外 {
   // クッキーが不良です...そのままにしておきます
  }
 }
}

解析後、次のステップは、parseSessionCookiesId メソッドで JSESSIONID という名前の Cookie をトラバースして一致させることです。存在する場合、その値はリクエストの requestedSessionId に設定され、内部セッション オブジェクトに関連付けられます。

2. セッションCookieを生成する

セッションに関連する Cookie は Tomcat 自体によって生成されます。セッション オブジェクトを取得するために Servlet で Request.getSession() が使用されると、実行がトリガーされます。コア コードは次のとおりです。

保護されたセッションdoGetSession(boolean create) {
 ...
 // セッションインスタンスを作成する if (connector.getEmptySessionPath() && isRequestedSessionIdFromCookie()) {
  // セッション ID が Cookie から取得された場合は再利用し、URL から取得された場合はフィッシング攻撃の可能性を防ぐため再利用しないでください。session = manager.createSession(getRequestedSessionId());
 } それ以外 {
  セッション = manager.createSession(null);
 }
 // セッションに基づいて新しいセッションCookieを作成する
 if ((セッション != null) && (getContext() != null)
    && getContext().getCookies()) {
  文字列 scName = context.getSessionCookieName();
  scName == nullの場合{
   //デフォルトのJSESSIONID
   scName = Globals.SESSION_COOKIE_NAME;
  }
  // 新しいクッキーを作成する
  クッキー cookie = 新しい Cookie(scName, session.getIdInternal());
  // パスドメインをセキュアに設定する
  セッションCookieを設定します。
  // レスポンス ヘッダー フィールドに追加 response.addSessionCookieInternal(cookie, context.getUseHttpOnly());
 }
 セッションが null の場合
  セッション.アクセス();
  戻り値(セッション)
 } それ以外 {
  戻り値 (null);
 }
}

レスポンスヘッダーフィールドに追加され、冒頭で説明した形式で Cookie オブジェクトに従って生成されます。

3. セッション

Session は Tomcat 内のインターフェイスであり、HttpSession のファサード クラスであり、Web アプリケーションの特定のユーザーのリクエスト間の状態情報を維持するために使用されます。関連するクラス図の設計は次のとおりです。

主要なクラスまたはインターフェースの機能は次のとおりです。

  • マネージャー - セッション プールを管理します。さまざまな実装により、永続性や分散などの特定の機能が提供されます。
  • ManagerBase - セッションプール、一意のID生成アルゴリズムなどのいくつかの基本機能を実装し、継承と拡張が容易です。
  • StandardManager - このコンポーネントの再起動後もシンプルなセッションの永続性を提供する標準実装 (たとえば、サーバー全体をシャットダウンして再起動した場合や、特定の Web アプリケーションを再ロードした場合など)
  • PersistentManagerBase - ファイルやデータベースなどのさまざまな永続ストレージ管理方法を提供します
  • ストア - セッションとユーザー情報の永続的な保存と読み込みを提供します
  • ClusterManager - セッションレプリケーションを担当するクラスタセッション管理インターフェース
  • DeltaManager - セッションデータをクラスタ内のすべてのメンバーに段階的に複製します
  • BackupManager - データを 1 つのバックアップ ノードにのみ複製し、クラスターのすべてのメンバーから見えるようにします。

この記事では、クラスター レプリケーションの原理は分析せず、スタンドアロン セッションの管理のみを分析します。

3.1 セッションの作成

最初に Request.getSession() を使用してサーブレット内のセッション オブジェクトを取得すると、StandardSession インスタンスが作成されます。

パブリックセッションcreateSession(String sessionId) {
 // デフォルトの戻り値は new StandardSession(this) インスタンス Session session = createEmptySession(); です。
 // プロパティを初期化する session.setNew(true);
 セッションをtrueに設定します。
 セッションの作成時間を設定します。
 // セッションの有効期間を秒単位で設定します。デフォルト値は 30 分です。負の値は有効期限がないことを意味します。session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
 セッションIDがnullの場合
  // セッションIDを生成する
  セッションID = 生成セッションID();
 
 セッションIDを設定します。
 セッションカウンタ++;

 SessionTiming タイミング = new SessionTiming(session.getCreationTime(), 0);
 同期 (セッション作成タイミング) {
  セッション作成タイミングを追加します。タイミングを指定します。
  セッション作成タイミング.poll();
 }
 戻り値(セッション)
}

鍵となるのは、セッションの一意の識別子の生成です。Tomcat の生成アルゴリズムを見てみましょう。

  1. ランダムに16バイトを取得する
  2. これらのバイトをMD5で暗号化し、再び16バイトの配列を取得します。
  3. 新しいバイト配列を走査し、各バイトの上位4ビットと下位4ビットを使用して16進文字を生成します。
  4. 最後に、32 ビットの 16 進文字列が得られます。

コアコードは次のとおりです。

保護された文字列generateSessionId() {
 バイトランダム[] = 新しいバイト[16];
 文字列 jvmRoute = getJvmRoute();
 文字列結果 = null;
 // 結果を 16 進数の文字列としてレンダリングします。StringBuffer buffer = new StringBuffer();
 する {
  int 結果の長さバイト = 0;
  if (result != null) { // 繰り返し、再生成 buffer = new StringBuffer();
   重複++;
  }
  //sessionIdLengthは16です
  (resultLenBytes < this.sessionIdLength) の場合 {
   getRandomBytes(random); // ランダムに 16 バイトを取得します // デフォルトでは MD5 を使用して、これらの 16 バイトの要約を取得します
   ランダム = getDigest().digest(ランダム);
   // このバイト配列を走査し、最終的に 32 ビットの 16 進文字列を生成します。for (int j = 0;
   j < random.length && resultLenBytes < this.sessionIdLength;
   j++) {
    // 指定されたバイトの上位 4 ビットと下位 4 ビットを使用して 16 進文字を生成します。byte b1 = (byte) ((random[j] & 0xf0) >> 4);
    バイト b2 = (バイト) (ランダム[j] & 0x0f);
    // 16進数に変換 if (b1 < 10) {buffer.append((char) ('0' + b1));}
    // 16進文字を大文字に変換します。else {buffer.append((char) ('A' + (b1 - 10)));}
    
    b2 < 10 の場合 {buffer.append((char) ('0' + b2));}
    そうでない場合は{buffer.append((char) ('A' + (b2 - 10)));}
    結果の長さバイト数++;
   }
  }
  jvmRoute != null の場合 {buffer.append('.').append(jvmRoute);}
  結果 = buffer.toString();
 } while (sessions.containsKey(result));
 返す(結果)
}

3.2 セッション有効期限チェック

1 つの Web アプリケーションは 1 つのセッション マネージャーに対応します。つまり、StandardContext 内に Manager インスタンスが存在します。各コンテナ コンポーネントはバックグラウンド スレッドを開始し、定期的に自分自身と内部コンポーネントの backgroundProcess() メソッドを呼び出します。マネージャーのバックグラウンド処理は、セッションの有効期限が切れているかどうかを確認することです。

チェックのロジックは、すべてのセッションを取得し、それらの isValid を使用して期限切れかどうかを判断することです。コードは次のとおりです。

パブリックブール値isValid() {
 ...
 // アクティブかどうかを確認するかどうか。デフォルトは false
 ACTIVITY_CHECK && accessCount.get() > 0 の場合 {
  true を返します。
 }
 // 時間が経過したかどうかを確認します if (maxInactiveInterval >= 0) { 
  長いtimeNow = System.currentTimeMillis();
  int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
  (timeIdle >= maxInactiveInterval)の場合{
   // 期限切れの場合は、内部処理を実行します // 主に期限切れイベントに関心のあるリスナーに通知します
   有効期限が切れます(true);
  }
 } // 複数形は期限切れにならない return (this.isValid);
}

3.3 セッションの永続性

永続性とは、メモリ内のアクティブなセッション オブジェクトをファイルにシリアル化するか、データベースに保存することを意味します。セッション管理コンポーネントが準拠しており、永続性が有効になっている場合、保存はライフサイクル イベントの停止メソッドで実行され、読み込みは開始メソッドで実行されます。

ファイルへの永続化。StandardManager は、ファイルへの永続化機能も提供します。セッション プール内のすべてのアクティブなセッションを CATALINA_HOME/work/Catalina/<host>/<webapp>/SESSIONS.ser ファイルに書き込みます。コードは doUnload メソッド内にあります。

FileStore は、ファイルへの永続化機能も提供します。StandardManager との違いは、各セッションを <id>.session という名前の単一のファイルに書き込むことです。

データベースに永続化し、シリアル化されたバイナリ データを含むセッション関連データをテーブルに保存します。テーブル フィールド情報は次のとおりです。

tomcat_sessionsテーブルを作成する(
 session_id varchar(100) NULLでない主キー、
 valid_session char(1) not null, -- 有効期間 max_inactive int not null, -- 最大有効期間 last_access bigint not null, -- 最終アクセス時間 app_name varchar(255), -- アプリケーション名(/Engine/Host/Context 形式)
 session_data mediumblob、--バイナリデータ KEY kapp_name (app_name)
);

注:データベース ドライバーの jar ファイルを $CATALINA_HOME/lib ディレクトリに配置して、Tomcat 内のクラス ローダーから見えるようにする必要があります。

4. まとめ

この記事では、Tomcat のセッション管理について簡単に分析します。もちろん、多くの詳細は無視されています。興味のある方は、ソース コードを詳しく調べてください。Tomcat クラスター セッションの実装については、後で分析します。

要約する

以上がこの記事の全内容です。この記事の内容が皆様の勉強や仕事に何らかの参考学習価値をもたらすことを願います。123WORDPRESS.COM をご愛顧いただき、誠にありがとうございます。

以下もご興味があるかもしれません:
  • Tomcatはセッション共有(セッションレプリケーション)を実装します
  • Tomcat クラスタとセッション レプリケーション アプリケーションの概要
  • TomCatセッション管理の詳細な分析
  • nginx+tomcatは負荷分散を実装し、redisセッション共有を使用します
  • Tomcatのセッション管理メカニズム
  • Java での Tomcat memecached セッションの共有同期の問題の解決方法
  • Tomcat でのセッション実装の概要
  • Tomcatセッション管理分析についての簡単な説明
  • Nginx+Tomcat によるセッション管理の実装
  • Tomcat でタイムアウトしたセッションを監視および削除する方法

<<:  Windows 環境に mysql-8.0.11-winx64 をインストールする際に発生する問題を解決する

>>:  JavaScriptはフォームデータの非同期取得を実装します

推薦する

JavaScriptの無限ループを検出して防止する方法の詳細な説明

目次序文for文の無限ループを修正while文の無限ループを修正要約する序文Js デッド ループはど...

Vue における ref と $refs の紹介と使用例

序文JavaScript では、document.querySelector("#demo...

Linux リモート開発に vs2019 を使用する方法

通常、Linux プログラムを開発する場合、次の 2 つのオプションがあります。 Linux上で直接...

backgroundImage を使用して画像カルーセルの切り替えを解決する詳細な説明

単一のDOMノードでカルーセルを実装するbackgroundImage を使用すると、複数の画像を追...

スプレッド演算子のサンプルコードと JavaScript での応用

スプレッド演算子を使用すると、式をある時点で展開できます。スプレッド演算子は、複数のパラメーター (...

JavaScript スクリプトが実行されるタイミングの詳細な説明

JavaScript スクリプトは HTML 内のどこにでも埋め込むことができますが、いつ呼び出され...

魔法のMySQLデッドロックトラブルシューティング記録

背景MySQL のデッドロックについて言えば、私は以前 MySQL のロックに関する基本的な紹介記事...

複数のドメイン名、ポート、IP仮想ホストに基づくNginx構成

1. タイプの導入1.1 ドメインベースの仮想ホスティングいわゆるドメイン名ベースの仮想ホストとは、...

クールな点滅アラームボタンをおすすめします

効果は以下のとおりです。 コードは次のとおりです (クリックすると展開してソース コードが表示されま...

MySQL スロークエリログの設定と使用方法のチュートリアル

序文MySQL スロー クエリ ログは、日常業務でよく遭遇する機能です。MySQL スロー クエリ ...

Linux環境にDocker環境をインストールする(落とし穴なし)

目次インストールの前提条件ステップ1: システムの残りを確認してクリアし、Dockerの依存関係をイ...

MySQL 8.0.22 解凍版インストールチュートリアル(初心者向け)

目次1. リソースのダウンロード2. ソフトウェアを解凍する2.1 場所を選択する2.2 名前を変更...

携帯電話番号の歩数記録を取得するWeChatアプレット

序文最近、小さなプログラムを開発しているときに、このような問題に遭遇しました。ユーザーが認証をクリッ...

CSS3 フィルター属性の使い方の紹介

1. はじめにフロントエンドページのアニメーション効果を記述する場合、filter 属性は多かれ少な...

ESXI の仮想マシンにワークステーションをインストールするときに発生するネットワーク障害の解決策

問題の説明ESXI で Windows にワークステーションをインストールした後、内部の仮想マシンは...