Node.js + Express + MongoDB でのセッション管理

なんでMongoDBでセッション管理するのか

Node.js + Express (Connect) で標準で提供されている MemoryStore でセッション管理を行うとメモリ上での管理になるため node が落ちるとセッションデータが消えることになりセッションの永続化ができませんし、動作確認もソースの確認も行っていませんが、production で起動した際に出力されるメッセージ(https://github.com/senchalabs/connect/blob/master/lib/middleware/session.js#L199)に

Warning: connection.session() MemoryStore is not
designed for a production environment, as it will leak
memory, and will not scale past a single process.

とあり、どうやらメモリリークするらしいです。

そこで Node.js (JavaScript) と相性のいいNoSQLデータベースのオープンソース実装であるMongoDBでセッション管理を行います。MongoDB で Node.js + Express (Connect) のセッション管理を行うために connect-mongo パッケージを使用します。

今回使用する MongoDB では connect-mongo を使用しますが、Amazon DynamoDB で connect-dynamodb、Redis で connect-redis を使用するなど、他にもいくつかの選択肢が存在します。

検証環境

  • Node.js 0.8.12
  • Express 3.0.0rc5
  • connect-mongo 0.2.0

モジュールの読み込み

var express = require('express');
var app = express();
var MongoStore = require('connect-mongo')(express);

セッションストアの設定

app.configure(function(){
    // セッションIDをクライアントの cookie にセットするので必要
    app.use(express.cookieParser());
    // セッションストアを設定
    app.use(express.session({
        secret: 'topsecret',
        store: new MongoStore({
            db: 'databaseName', // require
            host: 'databaseHost', // default: 127.0.0.1
            username: 'username', // optional
            password: 'password', // optional
            clear_interval: 60 * 60 // Interval in seconds to clear expired sessions. 60 * 60 = 1 hour
        }),
        cookie: {
            httpOnly: false,
            // 60 * 60 * 1000 = 3600000 msec = 1 hour
            maxAge: new Date(Date.now() + 60 * 60 * 1000)
        }
    }));
});

セッションの設定は以上です。Node.js の待ち受けポートにアクセスすると無条件でセッションが開始され、MongoDB の databaseName.sessions コレクションにセッションデータが作成されます。

セッションの操作

request.session.name = 'value'; // 値の設定
delete request.session.name; // 値の破棄
request.session.destroy(); // セッションの破棄

セッションIDの生成ロジック

ところで、Connect のセッション管理の中で、どのようなロジックでセッションIDが生成され、パラメータとして渡している secret が使用されているかというと、(Connect 2.5.0のソースを確認)

  • Crypto#randomBytes で 18byte のランダムのバイト列を生成する
  • 上記で生成したバイト列を base64 エンコードし 24byte の文字列を取得する(セッションID)

生成されたセッションIDをキーにしてメモリやDBに保存されますが、これがこのままブラウザに渡されるわけではありません。

  1. A. セッションID
  2. B. セッションIDの HMAC/SHA-256(使用する秘密鍵はsecret)を取得し、それをbase64 エンコード
  3. A + '.' + B = ブラウザに渡されるID

こうして生成(sign)されたIDがブラウザに渡され、実際に使用するときには上記の逆の手順をふみ(unsign)、セッションIDが同一secretで生成されていることを保証、つまりセッションIDの改竄検出が行われています。パラメータ secret の目的はこの改竄検出ということになります。

また、このセッションID生成ロジックを外部から変更する(セッションIDの生成ロジックをより望ましい形に変更する)方法は現在の Connect の実装では提供されていないので、もし必要があれば lib/middleware/session.js (https://github.com/senchalabs/connect/blob/master/lib/middleware/session.js#L202) をフォークして実装する以外に手はないようです。他言語/他フレームワークではこの部分は容易に変更できる実装が多い気がしますので、function をパラメータで受けとってくれたりするとうれしいのですけど。

参考

Node.js v0.8 APIリファレンス http://nodejs.jp/nodejs.org_ja/docs/v0.8/api/
Express APIリファレンス http://expressjs.com/api.html
connect-mongo GitHub https://github.com/kcbanner/connect-mongo
Connect リファレンス http://www.senchalabs.org/connect/