CodeIgniterの学習 4 - セッションをDBに保持するようにする (暫定版)1

(08/11/10追記)
http://d.hatena.ne.jp/dix3/20081110/1226243974 にまとめました。



(08/10/14追記 一部変更 ソース内のinput->cookieの読み出し箇所2カ所を


$session_id = $this->CI->input->cookie($this->sess_cookie);

$session_id = $this->CI->input->cookie($this->sess_cookie,true);
にし、xss_cleanにしました 。)


(08/10/18追記
http://d.hatena.ne.jp/dix3/20081018/1224275341
にこのエントリの補足事項を追加しました。併せて参照ください。)


(08/11/05追記
Version 1.7.0からは、標準のSession.phpでもセッションデータをDBに保存できる
ようになったみたいです。

Updated the Sessions class so that any custom data being saved gets stored to a database rather than the session cookie (assuming you are using a database to store session data), permitting much more data to be saved.

(意訳)
セッションクラスを更新したよ。
んで、いろいろぶち込みたいカスタムデータをクッキーじゃなくて、データベース側に保存できるようになったのだ。
これで多い日も安心だわ。

http://codeigniter.com/user_guide/changelog.html より引用)

http://codeigniter.com/user_guide/libraries/sessions.html を参考のこと。


ci_sessionsテーブルのsession_dataのカラム名をuser_dataにリネームすれば、
Version1.7.0のCodeIgniter標準にも移行できるはずです。

あるとすれば、 $this->db_session-> だったところを、$this->session->にする位か。
まあ、それも http://d.hatena.ne.jp/dix3/20081018/1224275341 とかでゴニョゴニョ弄れば、
吸収できるから気にしないでいいや。


俺は以後も取りあえずDB2_Sessionを使うけど、ある程度1.7.0が安定したら標準側も試してみるつもり。


なお以下の記述の概念自体は、1.7.0標準でも同義だと思います。

)


以上追記終わり


(以下エントリー)


CodeIgniterのセッション管理はデフォルトだとCookieにデータを保存する独特な方式をとっている。
前から気になっていたので、これを変更することにした。

セッションの管理方式にはそれぞれメリット・デメリットがあると思うが、
俺の脳内妄想判定での、メリットデメリットは以下の通り


方式1:クッキーにセッションデータを保存する(CI独自のセッション管理)

  • メリット:
    • サーバ側に負荷が掛からない
    • 複数WEBサーバで負荷分散する時も、サーバ側にデータを保存しないからセッション管理が楽
  • デメリット:
    • クッキーに持たせるのに抵抗あり
    • 保存できる長さ制限
    • 携帯では無理だったり


方式2:PHPのネイティブセッション管理方式(PHP標準)

  • メリット:
    • 基本ゆえノウハウがあちこちにある。
  • デメリット:
    • 複数WEBサーバで負荷分散する時は、nfsで共有、or memcachedで共有させないとうまくいかない
    • 共有サーバで、無対策だとセッションの保存場所が/var/lib/php/session とかでみんなと共有するのが嫌だ
    • 全然関係ない他人のプログラムとsession_idが重複したらかなりいやだ(MD5の128bitsだと場合によっては重複も起き得るよねぇ、、、ini_set("session.hash_function",1);なら起きにくいか?)


方式3:DB管理方式(libraries追加)

  • メリット:
    • DBでセッション管理
    • 複数WEBサーバで負荷分散しても同一のセッションを見やすい、というか何もしなくていい。
  • デメリット:
    • DBが必要
    • セッション見るたびにDBアクセスが必要なので、大量アクセス系だときつい?

ちなみに10台以上の負荷分散は別次元の話だと思っているんで、よく分からない。あくまで数台の世界で考えてます。



ウチの場合には、正直どれでもいい(専用サーバ & 同時使用人数今後も少なめ)ので、
方式3のDB管理方式 を採用。

ただし、気に入らない点もあったので、暫定的に一部手を入れた。


設置覚え書き:

入手ソース:
A) DB_Session - http://codeigniter.com/wiki/DB_Session/
B) DB2_Session - http://codeigniter.com/wiki/DB2_Session/

A)からダウンロードできるソース (http://codeigniter.com/wiki/File:db_sessions.zip/)
をはじめに導入したが、後で改良版のB)を見つけたので、

B)のソース内のクラス名をDB2_Session→Db_session に変えて、これを使っている。

(08/09/29追記・変更 ログイン認証でFreakAuthを使う関係で、ファイル名DB_Session.phpをDb_session.phpにし、クラス名とコンストラクタもDB_SessionをDb_session に変更した。)


設定方法:

http://codeigniter.com/wiki/DB_Session/ を見ればいいだけだが、一応メモ
ちなみにDBは既に設定済み(mysql5を使用)、DBの使用方法はマニュアル見れば分かるので割愛。

  1. http://codeigniter.com/wiki/File:db_sessions.zip からソースを入手
  2. 解凍したファイルのDB_Session.phpを、application/libraries/Db_session.phpとして設置する。
  3. application/config/autoload.php
    $autoload['libraries'] = array('database','Db_session');
    にする
  4. A)のリンク先説明内の2/ Configuration を参考に application/config/config.php
    $config['sess_use_database'] = TRUE;
    $config['sess_match_ip'] = TRUE;
    $config['sess_match_useragent'] = FALSE;
    にする。
  5. A)のリンク先説明内の3/ Databaseddl文を流してセッション管理テーブルを作る。
  6. ここまででもいいのだが、せっかくなので、B)のリンク先のソースをコピーして、Db_session.php内に貼り付ける。ただし、クラス名とコンストラクタのDB2_Session は Db_session に変更する。($this->db_session-> みたいに呼び出したいから)


これで、

<?php
//セッションに値を格納
$this->db_session->set_userdata('info', 'なんか入ってきた:' .date("H:i:s",time()) );

//セッションから取り出し
$data["info"] = $this->db_session->userdata('info');

//セッションの破棄
$this->db_session->sess_destroy();
 ?>


みたいにセッションが扱えるようになる。

これでもいいのだが、セッションの破棄の部分が

<?php
//上略
/**
 * Destroy the current session
 *
 * @access    public
 * @return    void
 */
function sess_destroy()
{
    setcookie(
                $this->sess_cookie,
                '',
                ($this->now - 31500000),
                $this->CI->config->item('cookie_path'),
                $this->CI->config->item('cookie_domain'),
                0
            );
}

//下略
 ?>

としか書いて無くて、セッションを消してもDBにはレコードが残りっぱなしで、
ガーベージコレクション(sess_gc)が呼ばれるまで、ゴミが残り続けて気持ちが悪いので、

<?php
//上略
/**
 * Destroy the current session
 *
 * @access    public
 * @return    void
 */
function sess_destroy()
{
    //$session_id = $this->CI->input->cookie($this->sess_cookie);//セッションid取得
    $session_id = $this->CI->input->cookie($this->sess_cookie,true);//セッションid取得 xss_cleanのフラグをtrueに
    if($session_id){
        $this->CI->db->where('session_id', $session_id);
        $this->CI->db->delete($this->session_table);//セッションのレコードを物理削除
    }
    setcookie(
                $this->sess_cookie,
                '',
                ($this->now - 31500000),
                $this->CI->config->item('cookie_path'),
                $this->CI->config->item('cookie_domain'),
                0
            );
}
//下略
 ?>

みたいに、sess_destroy() 呼び出し時は、速攻で物理削除するようにした。


(08/10/14 追記)

上記に追加したsess_destroy()内と、sess_read()内に、


$session_id = $this->CI->input->cookie($this->sess_cookie);
の箇所があるところを


$session_id = $this->CI->input->cookie($this->sess_cookie,true);
に変更しxss_cleanをtrueにしてみた。


(08/10/16 追記)
ちなみに、session_idは、画面遷移の度(db_sessionがロードされる度)に毎回変更される。
(当然session_dataの値は引き継がれる)

ログイン前→ログイン完了→ログオフするまでずっと同じsession_idではないので、セッション固定化攻撃は受けにくいはず。
もしこの挙動が嫌で、どうしても固定化したい時は、フォーラムの続き(http://codeigniter.com/forums/viewthread/75687/)を参照するとよい。



実験用コントローラーとビューを設置

いろいろなソースの実験用+完成時にタスクリストになるように、

application/controllers/welcome.php をコピーして
application/controllers/tasklist.php を作り、

application/views/welcome_message.phpをコピーして
application/views/tasklist_view.php を作った。

あと mod_rewriteでindex.phpを除去している。

.htaccess

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond $1 !^(index\.php|images|img|css|js|robots\.txt)
RewriteRule ^(.*)$ /index.php/$1 [L]

application/config/config.php

$config['index_page'] = "";
$config['uri_protocol'] = "REQUEST_URI";

てなかんじ。




動作確認ソース:

application/controllers/tasklist.php

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Tasklist extends Controller {
 function __construct()
 {
  parent::Controller();
 }

 function index()
 {
  $data["info"] = ($this->db_session->userdata('info')) ? 
    $this->db_session->userdata('info') : "中に誰もいませんよ";
  $this->load->view('tasklist_view',$data);
 }

 function hoge1()
 {
  //セッションに値を格納
  $this->db_session->set_userdata('info', 'なんか入ってきた:' .date("H:i:s",time()) );
  //セッションから取り出し
  $data["info"] = $this->db_session->userdata('info');
  $this->load->view('tasklist_view',$data);
 }

 function hoge2()
 {
  $this->load->helper('url');
  //セッションの破棄
  $this->db_session->sess_destroy();
  redirect('/tasklist');
 }
}
/* End of file tasklist.php */
/* Location: ./application/controllers/tasklist.php */
 ?>

application/views/tasklist_view.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Tasklistへようこそ</title>
<style type="text/css">
body {
 background-color: #fff;
 margin: 40px;
 font-family: Lucida Grande, Verdana, Sans-serif;
 font-size: 14px;
 line-height:26px;
 color: #4F5155;
}
a {
 color: #003399;
 background-color: transparent;
 font-weight: normal;
 text-decoration:underline;
}
h1 {
 color: #444;
 background-color: transparent;
 border-bottom: 1px solid #D0D0D0;
 font-size: 16px;
 font-weight: bold;
 margin: 24px 0 2px 0;
 padding: 5px 0 6px 0;
}
#msg_red{
 color:#dc143c;
 font-weight: bold;
 background: #ffbbcc;
 border:1px solid #ffaacc;
 padding:10px;
 font-size:1.4em;
}
</style>
</head>
<body>
<h1>Tasklistへようこそ!</h1>
<p>↓セッションの値確認↓</p>
<p id="msg_red"><?= $info ?></p>
<ul>
<li><a href="/tasklist/hoge1">
/tasklist/hoge1 に行ってセッションを格納してみる
</a></li>
<li><a href="/tasklist/hoge2">
/tasklist/hoge2 でセッションを破棄して/tasklistにリダイレクト
</a></li>
</ul>
</body>
</html>

画面も貼っておく

まだまだすごくしょぼい。道のりは長い。
(初回:セッションデータ無し)
(hoge1:セッション格納後)



さあ、次いってみよう。

次は何しよう。
ああ、あれだ、xajaxかその代替品の組み込みだ。