CakePHPで画像認証
パスワードリマインダーや会員登録フォーム等で、人間か機械を判別する為に画像認証というものが有ります。皆さんもよく見かけると思いますし、正直面倒ですよね(^-^; でも、WEBアプリケーションを安全に運用する為には必要なものです。
今回はこの画像認証をCakePHPに実装する方法のご紹介です。といっても、検索するとたくさんの方が紹介されていますので、あくまで一例として。
入力が面倒にならないよう、1/2/3だけにしてあります。
Securimageのダウンロード
今回はSecurimageというライブラリを使いますので、こちらからファイル一式をダウンロードします。作者の方には此の場を借りて御礼申し上げます。
解凍したファイルをフォルダーごと vendors/に入れます。
※app/vendor/と2つ有りますが、どちらでも大丈夫なようです。
コントローラーの作成
今回は /Reminderに作りますので、それに応じたコントローラーを作成します。
Controller/ReminderController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <?php App::import( 'Vendor' , 'Securimage' , array ( 'file' => 'securimage/securimage.php' )); //ライブラリのインポート class ReminderController extends AppController { public $components = array ( 'Session' , 'Securimage' ); /* * フォームのページ */ public function index() { } /* * 認証画像の表示 */ public function CaptchaRender() { $this ->Securimage->sesid = $this ->Session->id(); $this ->Securimage->render(); } /* * 入力された値と検証 */ public function CaptchaCheck() { $this ->autoRender = false; if (! $this ->Session->read( 'securimage_code_disp.default' )) return false; $check = false; if ( $this ->data[ 'captcha' ] == $this ->Session->read( 'securimage_code_disp.default' ) ){ $check = true; } header( "Content-type: application/xml" ); echo '<?xml version="1.0" encoding="UTF-8" ?> ' . "\n" ; echo '<rss>' . "\n" ; echo ' <check>' . $check . '</check>' . "\n" ; echo '</rss>' . "\n" ; exit ; } } |
http://cake.studio-key.com/Reminder/CaptchaRender へアクセスすると解りますが、Reminder/CaptchaRender/の場合は認証画像を表示させます。
Reminder/CaptchaCheck/の方は、入力した値と生成された画像認証の文字列が一致しているかどうかを判断し、xml形式で表示します。※autoRenderはオフにしておきます。
ビューの作成
View/Reminder/index.ctp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <script type= "text/javascript" > jQuery(document).ready( function (){ jQuery(document).on( 'click' , '#button' , function (e){ jQuery.ajax({ type: "POST" , url: "<?php echo $this->webroot; ?>Reminder/CaptchaCheck/" , data: "captcha=" +jQuery( "#captcha" ).val(), success: function (xml){ if ( jQuery(xml).find( "check" ).text() != 1 ){ jQuery( '#message' ).html( '画像認証コードが一致しません' ).css({ "color" : "red" }); } else { /* * 実際の運用時はメッセージを出すのではなく、ここでPOST処理させる等 */ jQuery( '#message' ).html( '認証しました' ).css({ "color" : "blue" }); } } }); }); }); </script> <form id= "form" name= "form" method= "post" style= "width: 400px;" > <p >画像認証</p> <p> <img src= "<?php echo $this->webroot; ?>Reminder/CaptchaRender/?sid=<?php echo md5(uniqid()) ?>" id= "siimage" > //?sid=<?php echo md5(uniqid()) ?> の部分が脆弱性チェックをすると引っかかる事が有ります。無くても動作はするので消してもいいかも <a href= "#" onclick= "document.getElementById('siimage').src = '<?php echo $this->webroot; ?>Reminder/CaptchaRender/?sid=' + Math.random(); this.blur(); return false" >読み取りにくい場合</a> <br /> <input name= "captcha" type= "text" id= "captcha" size= "15" > <span id= "message" ></span> </p> <p><input type= "button" id= "button" value= "確認 ≫" class = "sendbutton" ></p> </form> |
画像認証部分はこんな感じで。ボタンを押すとjQueryでReminder/CaptchaCheck/へ問い合わせ、結果によって処理します。jQuery非同期通信を使っていますので、当然jQueryは読み込ませている前提です。ただ、非同期通信はこの場合、確認する為の場所がURLとして解ってしまうので、実際はPOSTしたときに確認した方が安全かな、と思います。また、jsコードをビューに直で書いてますが、外部ファイルにするなど、柔軟に。
コンポーネントの作成
Controller/Component/SecurimageComponent.php
今回はコンポーネントに入れましたが、好みなのでVendorに適当なファイルを作ってインポートしても良いと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?php class SecurimageComponent extends Component { var $sesid ; function render(){ $securimage = new Securimage(); $securimage ->ttf_file = ROOT . '/vendors/securimage/AHGBold.ttf' ; $securimage ->charset = '123' ; //画像に表示する文字列 default 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789' $securimage ->session_name = $this ->sesid; //★ $securimage ->show(); } } |
基本的にはフォントファイルのパスだけ設定しておけば大丈夫だと思うのですが、Securimage単独でsessionが発行されてしまう場合があるようで、コントローラー側から渡されたCakePHPのsession_idを指定して有ります。★部分
2014-08-05 追記
debugを0にするとsession_nameを指定していてもSecurimage側でsessionが発行されてしまう事があるようです。サンプルはdebug=0で正常に動作していますが、現在制作しているアプリケーションはcakephpのコアファイルを移動しており、このあたりが原因のようです。securimage.phpの960行目付近にあるsession_start();をコメントアウトする事で回避しました。
1 2 3 4 5 6 7 8 9 | if ( $this ->no_session != true) { // Initialize session or attach to existing if ( session_id() == '' || (function_exists( 'session_status' ) && PHP_SESSION_NONE == session_status()) ) { // no session has been started yet (or it was previousy closed), which is needed for validation if (! is_null ( $this ->session_name) && trim( $this ->session_name) != '' ) { session_name(trim( $this ->session_name)); // set session name if provided } //session_start(); } } |
本当はこんな感じで読み上げもしてくれるライブラリで、これも付けたかったのですが、webrootに設置しないと動作しないような感じで諦めました。コンポーネントにして云々って運用を考えないのであれば、付属されているサンプルを参考にしつつ、実装出来ると思います。
画像認証のライブラリは、基本的に画像に表示された文字列がsessionに書き込まれていますので、それとPOSTされた値を検証するというシンプルな仕組みのものが多いと思います。