CGI::Session.pmでセッション管理
はじめに
perlでCGI::Session.pmを使ってセッション管理を行う.idの生成・抹消をはじめ,セッション管理に必要な様々な機能を提供してくれ,非常に便利である.
CGI::Session.pmは必要なデータをサーバに置く.すなわち,セッションidを生成した後,サーバ内にidに対応するファイル(DBを指定することも可能)を生成する.セッションで必要なデータはサーバのファイルに保管し,プログラム間を行き来するのはセッションidのみである.
CGI.pmも一緒に使うと利便性はさらに高くなる.
動作環境
動作環境は以下の通りである.Windows XP上でWindows版のApache,cygwin版のperlが動いている.
- Windows XP Professional
- Apache 2.0.48(Windows版)
- cygwin 1.5.7-1
- perl 5.8.0
インストール
Windows XP/Apache/cgywin/perlのインストールは省略する.インストーラの言う通りでインストールできる.perlもcygwinのセットアッププログラムでインストールできる.
では,何をインストールするのかといえば,CPANを使ってCGI::Session.pmのインストールを行う.CPANはperlのモジュールのインストールを行うもので,CGI::Sessionはperlのモジュールである.CPANの設定を行う必要があるが,CPAN 初級を参考にしてほしい.Googleで「perl cpan」をキーワードにして検索しても参考になるサイトはいっぱいある.
CPANの設定が終わったら,CGI::Session.pmをインストールする.手順は簡単だ.
$ perl -MCPAN -e shell cpan> install CGI::Session
これでインストールしてくれる.“/cygdrive/c/program: not found
”こんなエラーが出てインストールに失敗する場合は,空白のあるディレクトリが原因である.cygwinでcpanを使ってエラーを参照のこと.
CGI::Session.pm以外にCGI.pmもインストールする.一緒に使うと便利である.手順はCGI::Session.pmと同様である.
$ perl -MCPAN -e shell cpan> install CGI
サンプルの説明
二つの画面からなるセッションを想定する.最初の画面を経由したアクセスのみを正常のアクセスとし,直接次の画面を呼出したり,一定期間(1分)を経過した後のアクセスはエラーとする.
プログラムは
${APACHE_HOME}/cgi-bin/
以下に配置する.${APACHE_HOME}はApacheをインストールしたディレクトリである.
遷移元画面:urlパラメータでセッションidを渡す
遷移元の画面を表示するプログラム(session-cgi.pl)である.遷移先へはセッションidをurlパラメータに付加して遷移する.アクセスするurlは以下の通りとなる.
http://localhost/cgi-bin/session-cgi.pl
#session-cgi.pl #!c:/progra~1/cygwin/bin/perl.exe -w use strict; use CGI; use CGI::Session qw/-ip_match/; #異なるipからのアクセスは認めない #Tutrial.podは'-ip-match',Session.pmは'-ip_match'.多分'-ip_match'. my $session=CGI::Session->new(undef,undef,{Directory=>'./.session'}); #セッションidの生成.ディレクトリ.sessionは予め作っておく $session->expire('+1m'); #有効期限の設定.1分間 $session->param('name','john'); #セッション経由で引き渡す項目と値 #htmlの生成 my $cgi=CGI->new; print $cgi->header(-charset=>'UTF-8'), $cgi->start_html(-lang=>'ja', -encoding=>'UTF-8', -title=>'CGI.pm使用/urlパラメータ'), $cgi->p('session id: '.$session->id.'<br/>', 'name: '.$session->param('name').'<br/>', $cgi->a({href=>'http://localhost/cgi-bin/session.pl?CGISESSID='.$session->id},'next')), $cgi->end_html; #end
セッションidの生成は以下のように記述する.
my $session=CGI::Session->new(undef,undef,{Directory=>'./.session'});
第一引数のデータソースがundefなので,「ファイルに,デフォルトのシリアライザを使って,MD5のIDジェネレータでidを生成」する.第二引数もundefなので新規idを生成し,第三引数のディレクトリにidに対応するファイルを生成する.カレントディレクトリにある.session
ディレクトリ内にこのようなファイル(cgisess_セッションid)ができる.中に有効期間やセッションに格納したデータが入っている.なお,ファイルを格納するディレクトリ(今回の場合は.session)は予め作っておく必要がある.
セッションの有効期限や,セッション経由で次の画面へ引き渡すデータの設定は下記のように記述する.
$session->expire('+1m'); #有効期限の設定.1分間 $session->param('name','john'); #セッション経由で引き渡す項目と値
urlパラメータとして次画面へ引渡すはidのみである.有効期限やセッション経由で引渡す値は全て.sessionディレクトリ以下のファイルに入る.
詳細は,CGIアプリケーションにおける持続的なデータのセッション参照.
htmlの生成はCGI.pmを使用している.CGI.pmの使用方法は簡単なCGI(Common Gateway Interface)クラスが詳しい.
cookieでセッションidを渡す
これも遷移元の画面を表示するプログラムである.前のプログラムとは,セッションidの渡し方が異なっている.urlパラメータではなくcookieを使用している.cookieはCGI.pmの機能で操作している.アクセスするurlは以下の通りとなる.
http://localhost/cgi-bin/session-cookie.pl
#session-cookie.pl #!c:/progra~1/cygwin/bin/perl.exe -w use strict; use CGI; use CGI::Session qw/-ip_match/; my $session=CGI::Session->new(undef,undef,{Directory=>'./.session'}); $session->expire('+1m'); #有効期限は1分間 $session->param('name','john'); #セッション経由で引き渡す項目と値 my $cgi=CGI->new; print $cgi->header(-charset=>'UTF-8', -cookie=>$cgi->cookie(-name=>'CGISESSID', -value=>$session->id)), #cookieにセッションidを保管 $cgi->start_html(-lang=>'ja', -encoding=>'UTF-8', -title=>'CGI.pm使用/cookie'), $cgi->p('session id: '.$session->id.'<br/>', 'name: '.$session->param('name').'<br/>', $cgi->a({href=>"http://localhost/cgi-bin/session.pl"},"next")), #urlパラメータにはセッションid不要 $cgi->end_html; #end
セッションidを受取る遷移先画面
遷移先の画面のプログラムである.urlパラメータとcookie両方に対応している.
#session.pl #!c:/progra~1/cygwin/bin/perl.exe -w use strict; use CGI; use CGI::Session qw/-ip_match/; my $cgi=CGI->new; my $sid=$cgi->cookie('CGISESSID')||$cgi->param('CGISESSID')||undef; #1.cookieからCGISESSIDを探す #2.cookieから取れなかったらurlパラメータを探す. #3.どちらも取得できなかったらundef. my $session=CGI::Session->new(undef,$sid,{Directory=>'./.session'}); #4.取得したセッションidが有効ならそのまま.無効なら別のidを発番. print $cgi->header(-charset=>'UTF-8'), $cgi->start_html(-lang=>'ja', -encoding=>'UTF-8', -title=>'CGI.pm使用/遷移先'); if(defined $sid && $sid eq $session->id){ #cookieかurlパラメータから値を取得でき,かつ有効なid print $cgi->p('セッションは有効<br/>', 'session id: '.$session->id.'<br/>', 'name: '.$session->param('name')); } elsif(defined $sid && $sid ne $session->id){ #cookie,またはurlパラメータから値を取得できた.しかしidとしては無効 print $cgi->p('セッションは無効<br/>', '$sid: '.$sid.'<br/>', '$session->id: '.$session->id); #不要なidはさっさと消去 #先にcloseをしないと,deleteで #'(in cleanup) could not flush: Couldn't unlink .session/cgisess_CGISESSID' #が発生する.エラーが出てもファイルは消える. #closeは遅いらしい $session->close; $session->delete; } else{ #cookie,またはurlパラメータから値を取得できない. print $cgi->p('セッションは無効<br/>', '$sid: undefined'), } print $cgi->end_html; #end
セッションidの取得箇所をもう少し説明する.以下のコードである.
my $sid=$cgi->cookie('CGISESSID')||$cgi->param('CGISESSID')||undef;
cookieとurlパラメータからセッションidを取得しているが,cookieメソッドやparamメソッドはCGI.pmのものである.cookieを優先し,cookieでセッションidが取得できなかったらurlパラメータを探している.cookie・urlパラメータ両方にセッションidがなかった場合はundefとなる.
cookieメソッド・paramメソッドで取得したセッションidには,有効なものと無効なものがある点に注意が必要である.遷移元画面で有効期限を1分に設定している.これを越えた場合,セッションidは取得できたとしても,無効なidとなる.有効期限以外にも,セッションidを改竄した場合はセッションidは無効となる.urlパラメータもcookieも書換えることは簡単だ.セッションidの有効・無効の判定をする必要がある.それが次のコードとそれに続く条件判定である.cookie・urlパラメータから取得したidと,CGI::Sessionが生成したインスタンスのidを比べることで有効・無効を判定できる.
my $sid=$cgi->cookie('CGISESSID')||$cgi->param('CGISESSID')||undef; my $session=CGI::Session->new(undef,$sid,{Directory=>'./.session'}); #4.取得したセッションidが有効ならそのまま.無効なら別のidを発番. if(defined $sid && $sid eq $session->id){ #cookieかurlパラメータから値を取得でき,かつ有効なid <<省略>> } elsif(defined $sid && $sid ne $session->id){ #cookie,またはurlパラメータから値を取得できた.しかしidとしては無効 <<省略>> $session->close; $session->delete; } else{ #cookie,またはurlパラメータから値を取得できない. <<省略>> }
今回のコードでは,セッションidの有効・無効の組合せを以下の3種類に分類している.
- セッションidが取得可,かつ有効(正常ケース)
- セッションidが取得可,かつ無効(有効期限切れやidの改竄)
- セッションidが取得不可
「セッションidが取得可,かつ有効(正常ケース)」な場合,cookie・urlパラメータから取得したセッションid($sid)と,そのセッションidを使って生成したCGI::Sessionのインスタンスのidは同一値を持っている.
「セッションidが取得可,かつ無効(有効期限切れやidの改竄)」な場合,cookie・urlパラメータから取得したセッションid($sid)を使ってCGI::Sessionのインスタンスは生成できない.CGI::Sessionは指定した無効なidではなく,有効なidを持った新しいセッションインスタンスを生成する.したがって,cookie・urlパラメータから取得したidと新しいセッションのidは異なる.また,このケースの場合,新しいセッションidをこのプログラムの中で生成する.このidはプログラム終了後に削除したほうが安全である.セッションオブジェクトのclose/deleteメソッドを使用する.
cookie・urlパラメータ両方からセッションidが取得できなかった場合,「セッションidが取得不可」に該当する.メッセージを出して終了である.
CGI.pmを使わない場合
CGI::Session.pmはCGI.pmがなくても使用可能である.CGI.pmを使わない場合のソースを以下に挙げる.最初に結論を言うと,CGI.pmを使ったほうがはるかに楽だ.
#session-cgi.pl #!c:/progra~1/cygwin/bin/perl.exe -w use strict; use CGI::Session qw/-ip_match/; my $session=CGI::Session->new(undef,undef,{Directory=>'./.session'}); #ディレクトリ.sessionは予め作っておく $session->expire('+1m'); #有効期限は1分間 $session->param('name','john'); #セッション経由で引き渡す項目と値 my $sid=$session->id; my $name=$session->param('name'); print<<END Content-Type: text/html; charset=UTF-8 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja"> <head> <title>CGI.pm未使用/cgiパラメータ</title> </head> <body> <p> session id: $sid<br/> name: $name<br/> <a href="http://localhost/cgi-bin/session2.pl?CGISESSID=$sid">next</a> </p> </body> </html> END #end #session-cookie.pl #!c:/progra~1/cygwin/bin/perl.exe -w use strict; use CGI::Session qw/-ip_match/; my $session=CGI::Session->new(undef,undef,{Directory=>'./.session'}); $session->expire('+1m'); #有効期限は1分間 $session->param('name','john'); #セッション経由で引き渡す項目と値 my $sid=$session->id; my $name=$session->param('name'); #cookieの有効期間を1分に設定.これをやらないと,遷移先の画面ではurlパラメータにセッションidがあっても,期限の切れたcookieのセッションidを優先する. my($sec,$min,$hour,$day,$month,$year,$wday)=gmtime(time+60); #有効期限を1分に設定 my @week=('Sun','Mon','Tue','Wed','Thr','Fri','Sat'); my @months=('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'); my $expires=sprintf("%s, %02d\-%s\-%04d %02d:%02d:%02d GMT", $week[$wday],$day,$months[$month],$year+1900,$hour,$min,$sec); print<<END Set-Cookie: CGISESSID=$sid; expires=$expires Content-Type: text/html; charset=UTF-8 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja"> <head> <title>CGI.pm未使用/cookie</title> </head> <body> <p> session id: $sid<br/> name: $name<br/> <a href="http://localhost/cgi-bin/session2.pl">next</a> </p> </body> </html> END #end #session.pl #!c:/progra~1/cygwin/bin/perl.exe -w use strict; use CGI::Session qw/-ip_match/; my $sid=undef; if(defined $ENV{'HTTP_COOKIE'}){ ($sid)=$ENV{'HTTP_COOKIE'}=~/CGISESSID=(.+)/; } elsif(defined $ENV{'QUERY_STRING'}){ ($sid)=$ENV{'QUERY_STRING'}=~/CGISESSID=(.+)/; } my $session=CGI::Session->new(undef,$sid,{Directory=>'./.session'}); print qq(Content-Type: text/html; charset=UTF-8 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja"> <head> <title>CGI.pm未使用/遷移先</title> </head> <body>); if(defined $sid && $sid eq $session->id){ #cookieかcgiパラメータから値を取得でき,かつ有効なid my $sid=$session->id; my $name=$session->param('name'); print qq( <p> セッションは有効<br/> session id: $sid<br/> name: $name </p>); } elsif(defined $sid && $sid ne $session->id){ #cookieかcgiパラメータから値を取得できたが,idは無効 my $id=$session->id; print qq( <p> セッションは無効<br/> \$sid: $sid<br/> \$session->id: $id<br/> </p>); #不要なidはさっさと消去 #先にcloseをしないとdeleteで #'(in cleanup) could not flush: Couldn't unlink .session/cgisess_CGISESSID' #が発生する.エラーが出てもファイルは消える. #closeは遅いらしい $session->close; $session->delete; } else{ #cookieかcgiパラメータから値を取得でない print qq( <p> セッションは無効<br/> \$sid: undefined </p>); } print qq( </body> </html>); #end
参考
- Japanized Perl Resources Project
- CGI::Session.pmのマニュアルやCGI.pmのマニュアルを参照できる.
- ミスティーネットPerl・CGI講座ミスティーネットPerl・CGI講座
- CGI.pmを使わない場合のクッキーの使い方等.
- CPAN 初級
- CPANの基本的な使い方の説明