session_regenerate_id()がMySQLでセッションを管理した際に動かない!
PHPでセッションをMySQLの中で管理するようにして、ログイン認証を作っていたときの話。
セッションIDの固定化の対策として、session_regenerate_id()を使ったところうまく動かなくてハマった。
MySQLによるセッションの管理についてはこちらの記事でも触れていたとおり、SessionHandlerInterfaceを使用してデータベース側にセッションを保存するようにしている。
これに基づいて、セッションをデータベースで管理しているわけだが・・・。
session_regenerate_id()を行った際に、セッションIDが再生成されるセッションIDに何故か変更されない。
また、直そうと触っている内にセッション用のテーブルにレコードが挿入されない状態となってしまった。
最初、session_regenerate_id()の使い方を間違えているのかと思い、セッションの管理を従来どおりのファイル側管理に一度戻して(実際に書いているプログラムでは、ファイル形式とMySQL形式を切り替えられるようにしているので正確にはスイッチしただけだけど)試してみた。
やはり、記述には問題がないようで、ファイルで管理する場合ならば普通に動く。
では、やはりSessionHandlerInterfaceでMySQLにセッションを入れるときの記述に問題があると思いPHPのドキュメンテーションを読んでみた。
つらつらと書かれているので情報を拾い出すのが面倒くさかったが、その中にcreate_sidメソッドについての記述を発見。
public function create_sid(){ // available since PHP 5.5.1 // invoked internally when a new session id is needed // no parameter is needed and return value should be the new session id created // ... }
要は、新しいセッションIDを作る際に、このcreate_sid()メソッドでそれを作成するらしい。
(ってか、SessionHandlerInterfaceはPHP 5.4以降なのに、create_sidcreate_sidは5.5以降なんだね。。)
作成してくれるのではなくて、作成する。
自分で能動的にセッションIDを生成する必要がある。
まぁこれをそのまま組み込んでもあかんので、hash( ‘暗号化方式’ )とuniqid( rand(), 1 )でランダムなハッシュ値を返すように設定してみる。
Class My_Sessions_Handler implements SessionHandlerInterface { private $db; function __construct() { $this->db = new mysqli( 'host', 'user', 'pass', 'name' ); } ~~他に必要なメソッドは割愛~~ public function create_sid(){ $id = hash( '暗号化方式', uniqid( rand(), 1 ) ); return $id; } }
こ、これで・・・!!
う、動かねぇええええええええ!!!
正確に言うと、新しいセッションIDの作成そのものはできているらしく、冒頭で触れた1つ目の問題は解決したかように見えた。
だが、今度はsession_regenerate_id()をした後にセッション用のテーブルにレコードが挿入されなくなった。
・・・2つ目の問題の発生である。
レコード挿入ができない(=セッション管理ができない)。
余計に悪化した、とも言える。
やはり一筋縄ではいかない。
思考停止で書いてあるがままのソースを突っ込んでも意味がないことの典型パターンだ。
悪魔のささやきで、
「じゃあ、session_regenerate_id()を使うことを諦めようか?」
「それとも、手慣れたファイル管理の方にしてしまおうか?」
・・・とか頭に浮かんだけど、サイト制作ならまだしも流石にシステム作っているときにそれだと気持ち悪い。
そこで、改めてSessionHandlerInterfaceにsession_regenerate_id()が加わった際の各メソッドの動きを探ってみた。
まぁなんの難しいこともなく、各メソッドに適当にデバッグ用出力を入れるだけ。
結果的にこいつがどう流れていたかと言うと・・・。
- session_start()でセッションが開始され、データベースへの接続が開く(コンストラクタメソッド、openメソッド)
- セッションIDが発行される、つまりレコードが挿入される。(writeメソッド)
- セッション情報、つまりレコードが読み込まれる。(readメソッド)
- この時点で、$_SESSIONにセッション情報(データ含めて)が流れ込んでアクセス可能となる。
- データベースの接続が一度閉じられる。(closeメソッド)
- プログラム側でsession_regenerate_id()を行う。
- 【???】※後述
- create_sidを元に新しいセッションIDが発行される。
- 再度、2.~4.の処理が呼び出される。
- 最終的にデータベースの接続が閉じられる。(closeメソッド)
メソッドによっては出力が禁止されている部分もあるので、だいぶざっくりとしているがおおよそこんなところ。
で、「【???】※後述」と書いていた所。
session_regenerate_id()を呼び出す前のsession_start()で一度セッションの処理が終わっているため、セッションは開いているけどデータベース接続は閉じられている状態になっている。
また、session_regenerate_id()では、セッションのオープンは実行されない。
レコード挿入が行われないのはまさにこれが原因だった。
データベース接続が閉じられている状態でテーブルにレコードを挿入しようとしてもそれはできないよ、というごくごく単純なこと。
そこで、それを踏まえて以下のようにcreate_sid()メソッドの記述を修正し、実行。
Class My_Sessions_Handler implements SessionHandlerInterface { private $db; function __construct() { $this->db = new mysqli( 'host', 'user', 'pass', 'name' ); } ~~他に必要なメソッドは割愛~~ public function create_sid(){ $this->db->connect( 'host', 'user', 'pass', 'name' ); // データベースに再接続 $id = hash( '暗号化方式', uniqid( rand(), 1 ) ); return $id; } }
・・・・。
・・・問題なく動いた。
これでセッションIDの再生成がきちんと行うことができるようになり、session_regenerate_id()をした後でのセッション用のレコードの挿入も特に問題がなくなった。
実際の作業中は、データベース操作をオブジェクトを作って行っている都合上、他の所を見ていたりもしたため、解決までに少し遠回りしている。
文面で書くとだいぶあっさりしてるけどね。
まぁ結果良ければ全て良しで、自分の求める形にできて満足(´・ω・`)