CakePHP」カテゴリーアーカイブ

PHP 二重送信防止処理(サーバー側)

サーバ側での二重送信防止

前にも二重送信防止のことを書きました。
jQueryでの二重送信防止

前の二重送信の防止はjQueryで制御していました。

つまり、クライアント側で送信防止をしていたのです。

でももしかすると、javascriptが有効になっていない場合だってあるかもしれません。

そんなときに、Enterキーを

ケンシロウ並に連打!!!されたら、ひとたまりもありません。

不必要なデータがたくさん登録されてしまいます。。。

そんなときは、サーバ側で防止するしかありません。

サーバー側での二重送信防止のイメージ

二重送信防止

流れは以下の感じです。

  • 最初にランダムなトークン(適当な文字列)を生成
  • sessionとhtmlのhidden属性に保存する
  • 送信したいデータとともに、hidden属性に設定したトークンをPOSTする
  • POSTされてきたトークンとsessionに保存していたトークンとを比較
  • 同じであれば登録しsessionのトークン情報を破棄、違っていれば、エラー処理

二重送信防止

CakePHPで実装

app/controllers/main_controller.php

	function doublesubmit(){
		if(!empty($this->data)){
			// Tokenの比較
			if($this->Session->read('dstoken') == $this->data['Example']['dstoken']){
				unset($this->data['Example']['dstoken']);
				$this->Session->delete('dstoken');
				if($this->Example->save($this->data)){
					$this->flash('おーけーです', array('controller'=>'main', 'action' => 'doublesubmit') ,5);
				}else{
					$this->flash('えぬじーです', array('controller'=>'main', 'action' => 'doublesubmit'), 5);
				}
			}else{
				$this->flash('すでに送信済みです', array('controller'=>'main', 'action' => 'doublesubmit'), 5);
			}
		}

		// Tokenの発行&Sessionに保持
		$dstoken = $this->_rand(12); //ランダムな文字列を生成する関数用意
		$this->Session->write('dstoken', $dstoken);
		$this->data['Example']['dstoken'] = $dstoken;
	}

app/views/main/doublesubmit.ctp

<h1>二重送信防止</h1>
<?php
	echo $form->create('Example', array('url' => array('controller' => 'main', 'action' => 'doublesubmit')));
	echo $form->input('title', array('type' => 'text'));
	echo $form->input('place', array('type' => 'text'));
	echo $form->input('dstoken', array('type' => 'hidden'));
	echo $form->submit('送信');
	echo $form->end();
?>

ランダムな文字列の生成は下記のサイトのコードを使用させてもらいました。

てくめも

コンポーネント化?

CakePHPだとSecurityComponentを使えばいいという情報を見つけたが、
せっかくなので自分でコンポーネント化してみることにした。

※まだ動作確認はあまりしていません。。。
app/controllers/components/prevent_double_submit.php

<?php
/************************************************
 * PreventDoubleSubmitComponent
 * 		二重送信を防止する
 * Usage
 * Controller:
 * 		$conponentsに追加。$doublesubmitsにオプションの設定をする。
 * View:
 * 		$this->Form->input('Token.token', array('type' => 'hidden'));を追記する。
 *
 ************************************************/
class PreventDoubleSubmitComponent extends Object{

	public $controller = null;
	public $components = array(
		'Session',
	);
	/**
	 * @var
	 * model:string		モデル名
	 * token:string		view側のhidden属性に設定するfield名
	 * auto:bool		自動でtokenの設定&二重送信のチェックをする(true)かどうか
	 * url:array		デフォルトは現在のアクセスパス
	 * message:string	setFlashに設定する文字列
	 */
	public $option = array(
		'model' => 'Token',
		'token' => 'token',
		'auto' => true,
		'url' => array(),
		'message' => null,
		'token_length' => 10,
	);

	//===============================================
	// コールバックメソッド
	//===============================================
	function initialize(&$controller){
		$this->controller = &$controller;
		if(isset($this->controller->doublesubmits)){
			$this->option = am($this->option, $this->controller->doublesubmits);
		}
	}

	function startup(&$controller){
		if(!empty($this->controller->data)){
			extract($this->option);
			if($this->option['auto']){
				if($this->isDoubleSubmit()){
					if(!empty($message)){
						$this->Session->setFlash($message);
					}
					$u  = array(
						'controller' => $this->controller->params['controller'],
						'action' => $this->controller->params['action'],
						$this->controller->params['pass'],
					);
					if(!empty($url)){
						$u = am($u, $url);
					}
					unset($this->controller->data);
					$this->controller->redirect($u);
				}else{
					$this->Session->delete($token);
					unset($this->controller->data[$model][$token]);
				}
			}
		}
	}
	function beforeRender(&$controller){
		if($this->option['auto']){
			$this->setToken();
		}
	}

	//===============================================
	// メソッド
	//===============================================
	/**
	 * トークンを生成しセットする
	 * @return
	 */
	function setToken(){
		extract($this->option);
		$t = $this->rand($token_length);
		$this->Session->write($token, $t);
		$this->controller->data[$model][$token] = $t;
	}

	/**
	 * 二重送信のチェック
	 * @return bool
	 */
	function isDoubleSubmit(){
		$ret = false;
		extract($this->option);
		if(isset($this->controller->data[$model][$token])){
			if($this->Session->read($token) != $this->controller->data[$model][$token]){
				$ret = true;
			}
		}
		return $ret;
	}

	/**
	 * ランダムな文字列を生成する
	 * @param object $length [optional]
	 * @return string ランダムな文字列
	 */
	function rand($length = 8){
	    $char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
   		mt_srand();
    	$ret = "";
    	for($i = 0; $i < $length; $i++){
       		$ret .= $char{mt_rand(0, strlen($char) - 1)};
    	}
    	return $ret;
	}
}
?>

【追記 start 2011/04/26】==================================
2重送信のアクセスがときどきあるので、
PreventWSubmitComponentを更新してみました。
修正したものはこちらです。

※CakePHP2.xではセキュリティコンポーネントを使って2重送信の防止ができるのでそちらを利用したほうがいいかもしれません。
【追記 end 2011/04/26】==================================

CakePHP 1.3 TwitterKit Plugin

TwitterKit Plugin

OAuth認証に対応したTwitterに関するCakePHPのプラグインです。

ダウンロード&設置方法

下記からダウンロード。
設定方法も書かれています。
Pluginなのでapp/plugin以下にDL&展開したものを放り込むだけ。
TwitterKit ダウンロード

また、下記のリンク先にTwitterKitの作成者が勉強会の発表用に作ったスライドの資料があります。
第2回CakePHP勉強会@福岡 LT発表資料

実際に作成してみる

上の資料をもとに、実際にコードを入力して同じものを作成してみました。
無事に、ちゃんとリバースしたツイートができました^^

最初、わからなかった点を1つだけ。。。
userのログイン処理の部分です。
login.ctpの中で、
TwitterFormHelpler::oauthLink()
が呼ばれています。

そのままlogin.ctpを使う場合は、次のことに注意が必要です。
oauthLink()の中ではjQueryのコードをbuffer();しています。
したがって、どこかで、writeBuffer()する必要があります。
自分の場合はlogin.ctpに追記しましたが、layouts/default.ctpで出力した方がいいようです。

そうしないと、読み込み中と表示されたままになってしまいます。

Twitterでアプリケーションにログインさせたい場合は、
まず、アプリケーションの登録の設定で、Twitterをログインに使用するにチェックをいれます。
そんでもって、oauthLink($options)の$optionsの設定で
‘authenticate’ => true
にすればそれ用のlinkが作成されます。

他の機能も少しずつ使ってみようかと思います。

CakePHP 1.3 JsHelper

JsHelper

CakePHP 1.3のJsHelperを使ってみました。

1.3ではJavascriptHelperが非推奨となっています。

1.2までは、サポートしていたjavascriptのライブラリはprototypeのみでしたが、JsHelperで3つののライブラリが使えるようになっています。

JsHelperの設定

  1. javascriptライブラリを読み込む。
    $this->Html->script();
  2. キャッシュされたスクリプトを出力
  3. ControllerにJsHelperを追加する。

ライブラリの読み込み

<head>
<?php echo $this->Html->charset(); ?>
<title>
<?php __('Sample'); ?>
<?php echo $title_for_layout; ?>
</title>
<?php
    echo $this->Html->meta('icon');
    echo $this->Html->css('cake.generic');
    // javascriptライブラリの読み込み
    echo $this->Html->script('jquery-1.4.2.min');
    echo $this->Html->script('jquery-ui-1.8.2.min');
    echo $scripts_for_layout;
?>
</head>
<body>
…

    <?php echo $js->writeBuffer();?>
</body>

デフォルトでスクリプトはキャッシュされます。

それを表示するために</body>タグが終わる前までに、

$js->writeBurrer();

を実行しなければいけません。

ヘルパーの追加

class HogeController extends AppController{
    public $name = 'Hoge';
    public $helpers = array('Js' => array('Jquery'));
}

JsHelperの使い方

php5では、メソッドをつなげて使用できるけれど、php4では別々に書かないといけないようです。

php4

$js->get('#hoge');
$js->event('click', $eventCode);

php5

$js->get('#hoge')->event('click', $eventCode);

使用例

get

get($selector)

cssのセレクタを設定する。

effect

effect($name, $options = array())

  • $name
    ‘show’, ‘hide’, ‘fadeIn’, ‘fadeOut’, ‘slideIn’, ‘slideOut’
  • $options
    speed : ‘fast’, ‘slow’

デフォルトではbufferに保存されない

bufferに保存したい場合は、buffer()を使う。

drag

drag($options = array())

$options

  • handle : ???
  • snapGrid : 座標移動の間隔の設定 array(x, y)で縦横の間隔を設定
  • container : ドラッグ可能な範囲
  • start : ドラッグスタート時のイベント処理
  • drag : ドラッグ中のイベント処理
  • stop : ドラッグ終了時のイベント処理

event

event($type, $content, $options = array())

$options

  • wrap : 無名関数でラップする?(デフォルト:true)
  • stop : イベントをストップする?(デフォルト:true)
<h1>JsHelper</h1>
<div id="dragMessage">メッセージ</div>
<div id="dragArea" style="height:200px;width:600px;">
<span id="hoge">ドラッグ要素</span>

<?php
$onStart = '$("#hoge")
                 .css("color", "blue")
                 .text("開始します");
            $("#dragMessage")
                 .text("ドラッグ開始!");
';
$onDrag = '$("#hoge")
                 .css("color", "red")
                 .text("ドラッグ中");';
$onStop = '$("#hoge")
                 .css("color", "green")
                 .text("ドラッグ要素");
            $("#dragMessage")
                 .text("ドラッグ終了!");
';
$js->get('#hoge');
$result = $js->effect('fadeIn', array('speed' => 'slow'));
$js->buffer($result);
$js->drag(array(
    'container' => '#dragArea',
    'start' => $onStart,
    'drag' => $onDrag,
    'stop' => $onStop,
    'snapGrid' => array(10, 10),
    'wrapCallbacks' => false
));
$js->get("#hoge")->event('click', $js->alert('クリックしたな!!'));
echo $js->writeBuffer(array('inline' => false));
?>