|
|
 |
| >IT技術情報>php>PHP第3回:PHP応用
|
 |
 |
 |
 |
【 ページ 】 |
1 | 2
| 3
| 4
| 5
| 6 | 7
| 8
| 9
| 10
|
<<前のページへ
|
 |
6.セッション管理
| ここでは、PHP
4 からサポートされた「セッション」を管理するための機構の利用方法について説明します。
「セッション」を使えば、あるユーザーが接続し、サイトを巡回している間、それが同一のユーザーからの接続であることをプログラムから知ることができるようになります。この機能を用いることによって、パスワードでログインをするような種類のサイトの構築の際、開発が大変楽になります。
なお、PHP3
にはセッション機能はありません(自分で作るか、PHPLib というライブラリを使うことで実現することは可能です)ので、この章で扱う
PHP のバージョンは、PHP 4.0.3pl1 とします。
また、ここのページに書かれている内容はセッションハイジャックに対しては無力です。ここで言うセッションハイジャックとは、セッションIDを直接指定してアクセスする方法です。クラッカーは存在するであろうセッションIDを予想する/あてずっぽうで指定する、もしくは通信経路の途中で傍受する、ブラウザのキャッシュやクッキーなどを見る、などの方法でセッションIDを指定します。これをやられると、セッション内部のページに簡単にアクセスできるほか、個人情報の漏洩、サイトの性質によっては金額的な損害も発生することがあります。ここに書くことは、それらに対しても一切の責任を負わないことをご了承ください。
今のところセッションハイジャックに対して有効な手段はSSLによる経路の安全化と、ブラウザにキャッシュを残さないなどの運用面での対策ということになります。それでも、あてずっぽうによるセッションハイジャックに対しては無力です。セッションID以外にも何かひとつ情報をhiddenで持ちまわすということも、あてずっぽうがあたる確率を低めるという意味で対策となりうるでしょう。また、REMOTE_HOST
などが使えるかもしれませんが、正常な利用時に REMOTE_HOST が変わることもあり得るようです。
|
 |
| ■セッション管理の簡単な例 |
 |
セッションは、セッション
ID という、各ユーザー固有の ID をクッキー(もしくは、URLへの埋め込み)で保存し、各ページ間で共有することによって、同一のユーザからのアクセスかどうかを判断することによって実現されています。
上記の説明でピンと来た方はそれを念頭に下記を読んでください。そうでない方は実際に動作する例を見て、なにができるのかをしっかりおさえてください。
下のスクリプトの動作を完全に理解するまで、次には進まないでください。
| |
 |
<?
session_start();
if (!isset($count)) $count=0;
session_register("count");
$count++;
?>
<html>
<body>
あなたは <?= $count ?> 回目の訪問です。
<a href="<?echo $PHP_SELF ."?PHPSESSID=". $PHPSESSID?>">カウントを増やす</a>
</body>
</html>
|
 |
このようなページを作り実際に表示してみると、リロードや、リンクをクリックするたびにカウンタが増えていきます。
では、ここで何が行われているか、順を追って説明します。最初に
session_start 関数でセッションの開始処理をしています。セッション内のページでは、最初に必ず
session_start 関数を呼び出さなければなりません。次の if 文で、count
という変数(これがセッション間で共有されます。セッション変数と呼ぶこともあります。)が存在しているかどうかをチェックしています。そして、存在していなければ、値として
0 をセットしています。
次に、session_register
関数で、count という名前の変数を、セッション間で共有する変数名(セッション変数)として登録しています。こうすることによって、これ以後の同一セッション内のスクリプトで、count
という変数の値が変われば、共有している変数の値も変わるようになります。 最後に、$count++として、カウンタの値を一つ増やしています。count
は、session_register 関数で登録された変数となっていますので、セッションで共有されている
count の方の値も同様に 1 増えます。
2 回目以降のアクセスでは、最初の
if 文は、偽となり、$count=0 は実行されません。session_start 関数でセッション変数の取り込みが行われているからです。
そのセッションで共有する
count という変数の値は、 (デフォルトでは) /tmp に sess_ で始まるファイル名で保存されています。内容を見てみると、
| |
 |
count|i:4;
|
 |
のように、count の値が保存されている様子がわかるとおもいます。上記ファイルのファイル名の
sess_ に続く部分がセッション ID になっています。
なお、regist_session
で登録する変数として、配列や連想配列を指定することも可能です。
上記スクリプト中で、
$PHPSESSID という変数がいきなり現れていますが、これはセッション名と呼ばれます。PHPSESSID
という文字列は、php.ini 中の、 session.name で定義されており、デフォルトではこの変数名でセッション
ID にアクセスできるようになっています。当然、変更することもできます。スクリプト中でセッション名を表示するためには
session_name 関数を使用します。
ちなみに、$PHP_SELF という変数名は、現在実行中のスクリプトのファイルへの(URLとしての)パスが格納されています。
|
 |
| ■セッション関連の設定について |
 |
次にもう少し複雑なサンプルの実装をご紹介するのですが、その前にセッション関連の設定について触れておきたいと思います。
php.ini 中の [Session] セクションで各種設定をします。いくつかの項目に注目して解説したいとおもいます。
session.auto_start
session_start 関数を自動的に呼び出すかどうかを指定します。セッションに関係のないページでも開始されてしまう気がするので、無効(0)にするのが私は好きです。デフォルトは
0 です。
session.use_cookies
セッションの実現のためにクッキーを使用するかどうかを設定します。デフォルトでは
1 です。 クッキーを利用した場合は、ブラウザを終了してもセッション情報を保持することができます。URL
埋め込みの場合は、ブラウザを終了すると、セッション情報が(自動では)残らないので、PHPSESSID=....
という部分も含めて、ユーザが自分で残しておく必要があるのではないかと思います。 世の中にはクッキーを利用できないブラウザも当然存在します。従って、社内システムなど、閉じた世界向けのサイト以外ではクッキーが利用できることを前提とするべきではありません。
開発するときに、クッキー対応のブラウザをつかっていて、クッキー非対応のブラウザでもきちんと動作するかを確認する際、ブラウザの設定を変更するのが面倒であれば、こちらの値を
0 にして確認してもいいかとおもいます。
session.cookie_lifetime
クッキーをつかった場合のセッションの存続期間を指定します。サーバー側でのセッション情報の存続時間ではなく、クライアントのブラウザに保存されるクッキーの存続期間です。単位は秒です。0
を指定すると、ブラウザ終了時にクッキー情報も同時に消去されます。 0 以外だと、クッキーファイルにセッション情報が残ります。
session.gc_probability
サーバー側に蓄積されたセッション情報の破棄をおこなう処理を GC (ガーベージコレクション:ちり集め)といいます。gc_probability
では、GC が行われる確率を指定します。標準では 1 (%)の確率で行われます。GC のルーチンを自分で書く場合、テストの際にはこれを100
に設定すればよいかと思います。
session.gc_maxlifetime
デフォルトでは、ファイルを用いてセッション情報を保管していきます。そのファイルの存続期間です。GC
が行われる際、この値を参照し、それ以前からアクセスの無いファイルを削除していく処理がおこなわれるのだとおもわれます。
なお、GC の処理を行う関数を自分で定義する場合、関数の引数にこの値が渡されます。
session.use_trans_sid
PHP4 では、セッションに関連するページの HTML を出力する際にリンクに対して自動的にセッションIDをつけてくれる機能があります。それを使用するかどうかの設定をします。これによって、開発中にはクッキーのある/なしを意識せずに開発を行うことができます(通常であれば、
<a href="hogehoge.php?<?=SID?>">
のようにして、セッションIDをGETメソッドで渡してやる必要があるのですが、それを自動でやってくれます)。
このオプションを 1 にするためには、configure 時に--enable-trans-sid
をつけていなければなりません。phpinfo() で configure 時のオプションを確認してみてください。
|
 |
| ■認証に利用する |
 |
| さて、このようなセッション管理がおそらくもっとも使われるのがユーザー毎のページのカスタマイズと認証の部分でしょう。ユーザーが最初にホームページを訪れた時に、「ユーザー登録」をし、2
回目以降の訪問で、ユーザー ID とパスワードを用いて、そのユーザー専用の情報にアクセスする、という種類のサイトを構築したい場合です。
ここでは、実際に新規登録用のページと、その読者専用のページを作ってみたいと思います。
ここで作成するサンプルの背景を多少説明すると、データベース(PostgreSQL)の
myuser テーブルに、ユーザ ID とパスワード、そして画面の背景色を登録します。また、セッション管理用の
session テーブルにセッション ID、ユーザ ID 、パスワード他、各種情報を残していきます。つまり、セッション管理の方法として、通常のファイルを用いた方法ではなく、
PostgreSQL を用いて行う方法を使用します。
各テーブルは下記のような
SQL で作成しました。
| |
 |
-- セッション管理用テーブル
CREATE TABLE session (
sid varchar(32) primary key,
uid varchar(16) not null,
password varchar(16),
rawdata varchar(512),
rdate timestamp default 'now'
);
-- ユーザ情報登録用テーブル
CREATE TABLE myuser (
uid varchar(16) not null,
password varchar(16),
color varchar(8)
);
|
 |
また、テストした環境は RedHat Linux 6.2J, Apache-1.3.19
+ PHP4.0.3pl1 です。
なお、以下で上げる例のプログラムのアーカイブを置いておきます。
セッション管理サンプルプログラム
すべての require "session_handler.php"
を取り除けば、通常のファイルを 使ったセッション管理に切り替わります(さもなくば、PostgreSQL
を使用します)。 |
 |
| ■プログラム説明 |
 |
ユーザ登録用のフォーム(regForm.php, regExe.php)
からユーザ登録をします。そのとき、myuser テーブ ルに各種情報がセットされ、その後コンテンツページ
(contents.php)に Location: ヘッダを用いてフォ ワードします。コンテンツページとは、認証が完了した
後にアクセスできる、各ユーザ毎のページです。
注意すべき点としては、いきなり URL としてコンテ ンツページを指定した際は認証エラーの表示を出し、
セッション内のページにアクセスできないようにしないと いけない、という点です。
通常、INPUT タグ の HIDDEN 属性で名前とパスワー ドを各ページに伝播させる方法をとるのですが、
・ 各ページの遷移の際に、いちいち上記タグを書かないといけない
・ URL や HTMLソース中にパスワードが含まれてしまう
という点から見て、セッションを用いる方法の方が優 れています。
以下にプログラムを掲載しておきます。
regForm.php
| |
 |
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;CHARSET=EUC-JP">
</head>
<body>
<form action="regExe.php" method="POST">
ログインID:<input type="text" size="15" name="fLoginID"><br>
パスワード:<input type="password" size="8" name="fPassword"><br>
背景色(RGB):<input type="text" size="15" name="fBGColor" value="#"><br>
<input type="submit" value="新規登録">
</form>
</body>
</html>
|
 |
regExe.php
| |
 |
<?
if( $fLoginID == "" || $fPassword == "" ) {
echo <<<EOT
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;CHARSET=EUC-JP">
</head>
<body>
ログインID、もしくはパスワードが不正です。空文字列はダメです。
EOT;
exit;
}
require "session_handler.php";
session_start();
$sql = "select count(*) from myuser where uid='$fLoginID'";
$r = pg_exec($SessDBConn, $sql);
if( pg_result($r, 0, 0) != 0 ) {
session_destroy();
print "そのログインIDはすでに使用されています。";
exit;
}
$sql = "insert into myuser (uid, password, color) ";
$sql.= " values ('$fLoginID','$fPassword','$fBGColor')";
$r = pg_exec($SessDBConn, $sql);
// 下記をセッション変数に登録
session_register("sLoginID");
session_register("sPassword");
// セッション変数の値を更新する。
$sLoginID = $fLoginID;
$sPassword = $fPassword;
?>
<html><head>
<meta http-equiv="Content-Type" content="text/html;CHARSET=EUC-JP">
</head>
<body>
登録しました。<br><br>
<a href="contents.php">コンテンツへ</a>
</body></html>
|
 |
ログイン用のフォーム(login.php, loginExe.php)
ではユーザ ID とパスワードの入力を促し、myuser テーブルに格納されているデータと照合し、認証を行
います。正しければページを contents.php にフォワ ードします。
以下にプログラムを掲載しておきます。
login.php
| |
 |
<?
require "session_handler.php";
session_start();
?>
<html><head>
<meta http-equiv="Content-Type" content="text/html;CHARSET=EUC-JP">
</head>
<body>
<form action="loginExe.php" method="POST">
ログインID:<input type="text" size="15" name="fLoginID"><br>
パスワード:<input type="password" size="8" name="fPassword"><br>
<input type="submit" value="ログイン">
</form>
</body></html>
|
 |
loginExe.php
| |
 |
<?
require "session_handler.php";
session_start();
// myuser テーブルに fLoginID と fPassword の組が
// 存在するかどうかを調べる。
$sql = "select password from myuser where uid='$fLoginID'";
$r = pg_exec($SessDBConn, $sql);
if( pg_numrows($r) != 1 ) {
print "アカウントが違う、もしくはユーザ登録されていません。";
print '<a href="regForm.php">こちらから登録してください。</a>';
session_destroy();
exit;
}
if( pg_result($r, 0, "password") != $fPassword ) {
print "パスワードが違います。";
session_destroy();
exit;
}
// 下記をセッション変数に登録
session_register("sLoginID");
session_register("sPassword");
// セッション変数の値を更新する。
$sLoginID = $fLoginID;
$sPassword = $fPassword;
// ページをコンテンツに飛ばす
// クッキー使わない場合は、うまくできないみたいです。
// そういう場合は、リンク。
//header("Location: contents.php");
?>
<a href="contents.php">こちらから</a>
</body>
</html>
|
 |
各コンテンツページ(contents.php, contents2.php)
では、セッション変数に値が入っているかどうかの チェックを行うべきです。 また、データベース中に存在するユーザ
ID とパスワ ードの組と、セッション変数としてもたらされたユー ザ ID とパスワードの組の照合も行うのが望ましいと
言えます。
なぜなら、URL をブラウザのアドレス欄に直接指定す る方法でアクセスされた場合にパスワードを知らない
第三者がセッション内のページにアクセス出来てしま うためです。これは防がなければなりません。
また、php.ini の register_globals の状態によって はセッション変数が
URL パラメータによって上書きさ れる可能性も考えられます(PHP の実装次第ですが、
調べていません)。
また、すでにセッションが開始されている状態(ブラ ウザ側にクッキーが残っている状態)の場合には、
login.php や loginExe.php ページへのアクセスは、 contents.php
ページへフォワードしてやるのがいい かもしれません。なぜなら、その時点では既にセッシ
ョンが確立しており、あらためて入力してもらう必要 ないからです(この場合も、contents.php
で認証を 行っているほうが望ましいですね)。
同様の理由で index.php へのアクセスも、contents.php
にフォワードします(セッションが確立していな い場合は、login.php にフォワードします)。
では、contents.php を掲載しておきます(contents2.php はほぼ同じ内容です)。
contents.php
| |
 |
<?
require "session_handler.php";
session_start();
// いきなりこのページにアクセスがあった場合
if( !$sLoginID ) {
session_destroy();
header("Location: login.php");
exit;
}
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;CHARSET=EUC-JP">
</head>
<?
// パスワードのチェック
$sql = "select uid, password, color from myuser where ";
$sql.= " uid='$sLoginID' and password='$sPassword'";
$r = pg_exec($SessDBConn, $sql);
if( pg_numrows($r) != 1 ) {
print "不正なセッションです。";
exit;
}
$dbLoginID = pg_result($r, 0, "uid");
$dbPassword = pg_result($r, 0, "password");
$dbBGColor = pg_result($r, 0, "color");
echo <<<EOT
<body bgcolor="$dbBGColor">
〜 あなたの情報 〜<br>
ログインID : $dbLoginID<br>
パスワード : $dbPassword<br>
背景色 : #$dbBGColor<br>
<hr>
セッションID : $PHPSESSID<br>
EOT;
?>
<a href="contents2.php">次のページ</a> |
<a href="logout.php">ログアウト</a>
</body>
</html>
|
 |
ログアウトの処理は、session_destroy 関数を呼んで おきます。以下にプログラムを掲載しておきます。
logout.php
| |
 |
<?
require "session_handler.php";
session_start();
session_destroy();
header("Location: login.php");
?>
|
 |
ここまでのプログラムで、session_handler.php
とい うプログラムを require していましたが、これは PostgreSQL
を使ってセッション情報を管理するための ハンドラの記述です。下記に掲載しておきますので、
適宜内容を変更してお使いください。このファイルを require しなければ、通常通りファイルによる
セッション管理になります。
ただ、PostgreSQL は PHP4 のセッション管理への利
用には向いていないようです。なぜなら、セッション 情報の更新はsession_start
が呼ばれたページのスク リプトの終了時に発生するのですが、一人のユーザが 10 のページを表示したら、PostgreSQL
の構造上 10 行のデータが追加されます。そのうち、必要のない行 を削除するために
vacuum という操作を行わなければ ならないのですが、これだけ頻度の高い更新だと、
vacuum にかかる時間も多くなります。vacuum してい る間は、データベースを利用できないので、このサイ
トはその間止まることになってしまいます。
ですから、セッション管理には MySQL などを利用し たほうがいいのかもしれません。MySQL
は PostgreSQL に比べて特に機能面で見劣りがするので すが、実際セッション管理をするだけなら、MySQL
の 持つ機能で十分です。(ここでは PostgreSQL と MySQL の優劣を言っているわけではありません。対象
に対する向き/不向きを言っています。)
また、DB への接続コストが無視できないなら、 pg_pconnect などの持続的接続を使うのがいいかもしれません。
session_handler.php
| |
 |
<?
// キーが session テーブル中の項目名、値はセッション変数名
$sess_value = array( "uid" => "sLoginID",
"password" => "sPassword"
);
// セッション情報管理用データベースの名前
define(S_SESSION_DB, "mySession");
// セッション情報管理用テーブルの名前
define(S_SESSION_TABLE, "session");
// シリアライズされたセッションデータ保管用項目名。
// 読み出し用関数の作成が楽だったので、これも
// 保管するようにしちゃいました。
define(S_RAW_DATA_FIELDNAME, "rawdata");
// セッションID保管用項目名。
define(S_SID_FIELDNAME, "sid");
// セッション登録日保管用項目名。
define(S_RDATE_FIELDNAME, "rdate");
// 不要なセッションを何日残しておくか?
define(S_GC_DAYS, "30");
// SessDBConn は、セッション管理用データベースへの接続。グローバル変数です。
// 他のスクリプトからも参照しています。
// 今の場合、myuser テーブルも同一のデータベースにありますが、
// 同一でなくても構いません。その場合、他の各スクリプトにて
// 接続してやらなければならないでしょう。
if(!isset($SessDBConn)){
$SessDBConn=0;
function my_sess_open($garbage, $sid){
global $sess_value, $SessDBConn;
// error_log("connect\n", 3, "/tmp/uuu");
if(!$SessDBConn){
$SessDBConn = pg_connect("host=localhost dbname=".S_SESSION_DB)
or die("接続できません。");
if($SessDBConn == 0)return false;
}
}
function my_sess_close(){
global $sess_value, $SessDBConn;
// error_log("close\n", 3, "/tmp/uuu");
return true;
}
function my_sess_read($sid){
global $sess_value, $SessDBConn;
// error_log("read\n", 3, "/tmp/uuu");
if(!$SessDBConn)return false;
// echo "読み出し(read)関数 $sid -----<br>\n";
// read session data from table
$sql = "select ". S_RAW_DATA_FIELDNAME ." from ".S_SESSION_TABLE." where ";
$sql.= S_SID_FIELDNAME."='$sid'";
$result = pg_exec($SessDBConn, $sql);
if(pg_numrows($result) > 0) {
return pg_result($result, 0, S_RAW_DATA_FIELDNAME);
}
return false;
}
// セッション変数に変更があったときに呼ばれる。
function my_sess_write($sid, $data) {
global $sess_value, $SessDBConn;
// error_log("write\n", 3, "/tmp/uuu");
if(!$SessDBConn) return false;
session_decode($data);
// update する。この関数は何回も呼ばれる(セッション内で、ページの
// 遷移ごとに一回呼び出し)のが普通なので、update するのが基本です。
// ということで、セッション管理に PostgreSQL は、あまり向いていない
// かもしれません。なぜなら、vacuum にかかる時間が長くなるからです。
// 特に、アクセス数の多いサイトでは、絶望的です。その場合は MySQL
// 等を使いましょう。
// 連想配列をもとに、SQL を組み上げます。
$sub_sql = "";
foreach( $sess_value as $column_name => $session_value_name ) {
if( $sub_sql != "" ) {
$sub_sql .= ",";
}
$sub_sql .= "$column_name ='" . $GLOBALS[$session_value_name] . "'";
}
$sql = "update ".S_SESSION_TABLE." set $sub_sql,".S_RAW_DATA_FIELDNAME."='$data',";
$sql .= S_RDATE_FIELDNAME." ='now' where ".S_SID_FIELDNAME."='$sid'";
$result = pg_exec($SessDBConn, $sql);
// update で変更を受けた行が無かったら、insert する。
if( pg_cmdtuples($result) == 0 ){
// 連想配列をもとに、SQL を組み上げます。
$columns_sql = ""; $values_sql = "";
foreach( $sess_value as $column_name => $session_value_name ) {
if( $columns_sql != "" ) {
$columns_sql .= ","; $values_sql .= ",";
} else {
$columns_sql .= "("; $values_sql .= "(";
}
$columns_sql .= "$column_name";
$values_sql .= "'". $GLOBALS[$session_value_name] . "'";
}
$columns_sql .= "," . S_RAW_DATA_FIELDNAME. ",".S_SID_FIELDNAME;
$columns_sql .= ",".S_RDATE_FIELDNAME.")";
$values_sql .= ",'$data','$sid','now')";
$sql = "insert into ".S_SESSION_TABLE." $columns_sql values $values_sql";
$result = pg_exec($SessDBConn, $sql);
}
return true;
}
function my_sess_destroy($sid){
global $sess_value, $SessDBConn;
// error_log("destory\n", 3, "/tmp/uuu");
if(!$SessDBConn)return false;
// データベースから行を delete する。
$sql = "delete from ".S_SESSION_TABLE." where ".S_SID_FIELDNAME."='$sid'";
$result = pg_exec($SessDBConn, $sql);
// error_log("destory $sql\n", 3, "/tmp/uuu");
// 行が消えてなかったら、エラー。
if( pg_cmdtuples($result) == 0 ) return false;
return true;
}
function my_sess_gc($maxlife){
global $sess_value, $SessDBConn;
// $maxlife には、php.ini で指定した session.gc_maxlifetime の値が多分入っている。
// ここでは使わないことにします。
// error_log("gc\n", 3, "/tmp/uuu");
if(!$SessDBConn) return false;
$sql = "delete from session where " . S_RDATE_FIELDNAME;
$sql .= " < ('now'::timestamp + '-".S_GC_DAYS." day')";
pg_exec($SessDBConn, $sql);
return true;
}
session_set_save_handler("my_sess_open",
"my_sess_close",
"my_sess_read",
"my_sess_write",
"my_sess_destroy",
"my_sess_gc");
}
?>
|
 |
|
|
|
 |
【 目次
】
1.リロード対策クラス
2.PostgreSQL
3.MySQL
4.メール
5.メール用クラス
6.セッション管理(PHP4)
7.日付処理
Tips
8.配列処理
Tips
9.オブジェクト指向
10.PDF
関数の使用
|
 |
【 関連記事 】
|
|
 |
 |
|
サイト内全文検索 |
| スタックアスタリスクのサイトを検索します。検索には、Googleを利用しています。そのため、最新の情報で検索されない可能性があります。 |
|