バカンス駆動開発

この前バカンスって言ったら「古っ」って言われました

Ajaxこと始めまとめ

Ajaxこと始めまとめ

jQuery.ajax()のまとめ: 小粋空間

こと始めにはもってこいのエントリです。

jQuery.ajax(settings) - jQuery API 1.4.4 日本語リファレンス - StackTrace

どんなプロパティがあるか?を使い方と合わせて見やすくまとめられています。

サーバー側(PHP)

レスポンスのHTTPヘッダー

JOSNを返す場合

JSONを返す場合はしっかりヘッダにjsonである旨を記載しましょう。

Content-Type: application/json

理由はこちらのブログにとても詳しく書いてあります。有難うございます。 PHPのイタい入門書を読んでAjaxのXSSについて検討した(3)~JSON等の想定外読み出しによる攻撃~ - ockeghem(徳丸浩)の日記

またもIE

IEはContent-Typeヘッダを無視して、内容からコンテンツ形式を決定するようです。なので以下の設定で対策をします。

X-Content-Type-Options: nosniff

「ファイルの内容によってコンテンツの形式を決定すること」を禁止。 ただしIE7以下は無効。

Ajax通信か判定

まずはこちらのokwaveの質問を引用

Ajax通信の際のphp直アクセス防止について | PHPのQ&A【OKWave】

たとえば、いくらJS内で情報を隠蔽しようとも、Firebugなどのツールを使えば、送信先PHPのパス、データの内容、リクエストの種類などは簡単にのぞき見できます。まずこれが一つめです。そして、PHPに対して、JSからのリクエストを許可するということは、ブラウザからのリクエストを許可することと同義だと思っていること。(正確にはわかりません。)

もし、仮にこれらのことが同時にあったとすると、外部ドメインから、内部リクエスト(リクエストの種類、リファラー、クライアント属性やデータそのもの)を模倣された場合、どのように防ぐか、というのが今回の質問です。 直アクセスされたくないので、きとんとajaxでリクエストされたことをサーバーでチェックしたいです。

この質問はとても重要なことだと思いました。Ajaxは通信内容がだだもれで、かつjsが直接サーバー側のスクリプトを操作するので、通信内容からスクリプトを予測されやすい、ということでしょうか。

なので、Ajaxを使う場合、リクエストが確実にこちらが用意したAjax通信からきているという保証がほしい。どうしても。

HTTPヘッダの内容でチェックする

jQueryajax通信する場合、リクエストのHTTPヘッダに自動的に以下が付加されます。

X-Requested-With: XMLHttpRequest

なのでサーバー側でそれをチェックする以下のような方法があちこちで紹介されています。

<?php
if ( isset($_SERVER['HTTP_X_REQUESTED_WITH'])
    && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
    
    return true;
}

ですが、この方法はXHRオブジェクトの標準メソッドにより簡単に偽装(というか設定)できます。

//js側で設定
var xhrobj = new XMLHttpRequest();
xhrobj.setRequestHeader("X-Requested-With", "XMLHttpRequest");

なので他の方法による対策が必要です。

現実的な対策として2パターン

1.サーバー側で受け取ったリクエストのバリデーションをガチガチにする。

頑張りたいですね。

2. ワンタイムトークンを導入する

セッションを使ってワンタイムトークンを発行する手法が今のところ有用であるみたいです。ただその場合、複数回ajax通信を行うその画面内でトークンをどう管理するかという問題をまた考えないといけません。

これは宿題

(追記:こちらで書きました AjaxにおけるCSRF攻撃対策 - バカンス駆動開発

クライアント側

XSS対策

ajaxとは言え、jsによって出力しますので最後の出力手前できちんとエスケープする必要があります。 でも、これは心配ご無用でjQueryを通すと勝手にエスケープしてくれます。

例外処理

jQueryajaxを扱うには、$.ajaxとそのラッパーである$.post$.get$.loadというメソッドたちが用意されています。 ですが例外処理などのパラメータを持つのは$.ajaxだけですのでこれを使います。

ajaxはサーバーからのレスポンスが400系、500系、あるいは通信自体がうまく行かなかった場合エラーを感知します。

あと200 OKで返ってきてもdataTypeパラメータで設定した型と返り値が違っていたらエラーになります。サーバー側は200 OK出してるのにクライアントではエラーと判定します。これが初心者的にややこしかった。

エラー処理はerrorパラメータで行います。

$.ajax({
    url: url,
    type: json,
    data: {
        //data
    },
    success : function(data) {
        //成功時の処理
    },
    error : function (XMLHttpRequest, textStatus, errorThrown) {
        console.log(XMLHttpRequest); // XMLHttpRequestオブジェクト
        console.log(textStatus); // status は、リクエスト結果を表す文字列
        console.log(errorThrown); // errorThrown は、例外オブジェクト
    },
    complete : function() {
       //完了時の処理
    },

})

errorパラメータのコールバック関数の返り値について

textStatusは「error」という文字列が返ってきます。僕が試した所400系、500系、通信遮断いずれもそうでした。

errorThrownは「Not found」とか「Internal Server Error」とかレスポンスコードのテキストが返ってきます。通信自体が失敗した時は空でした。また、dataTypeと返り値の型が違う場は「parseerror」が返ってきます。

XMLHttpRequestは文字通りXHRオブジェクトが返ってきます。ステータコードをパラメータに持つので、それを見て画面へのエラー出力を振り分けることが出来ます。

入力エラー

リクエスト的にはOKだけど許可できない場合、例えばユーザー登録フォームで空送信するような場合の対応について。

考え方としては2つあって、1つはサーバー側で403 Forbiddenをだして、上記のerrorパラメータに振り分ける。HTTPステータス・コード - CyberLibrarian

もう1つは、レスポンスのjson値にエラーを検知するパラメータを付けて返すようにする。今回はこれをやってみます。

サーバー側

<?php
//ブラウザから空のフォーム送信を行われた

$errors = array();

//バリデーション処理
$errors[] = 'ユーザー名を入力して下さい';
$errors[] = 'メールアドレスを入力して下さい';

if ( count($errors) === 0 ) {
    //正常時の処理
    $res = array(
        //返り値
    );

} else {
    //エラー時
    $res = array(
        'errors' => $errors,    //返り値にエラー内容をセット
    )
}

header('Content-Type: application/json');
echo json_encode( $res );
exit;

次にクライアント側

//(略)
success : function(data, textStatus, jqXHR) {
                if (data.errors) {
                    //サーバーで設置したエラー値を元にエラー表示用HTMLを組み立てます。
                    return;
                }
                //正常時処理
},
//(略)

バリデーションがらみでついでに。

beforeSendというパラメータがあります。

jQuery.ajax(settings) - jQuery API 1.4.4 日本語リファレンス - StackTrace

リクエストが送信される前に実行するコールバック関数を指定します。 引数にXMLHttpRequestオブジェクトが渡されますので、ヘッダをカスタマイズしたい場合などに使うことができます。 このコールバック関数が false を返すと、リクエストを中止します。

function (XMLHttpRequest) { // falseを返すと、リクエストを中止 }

リクエスト送信前の処理を行うコールバックを書けます。注目すべきはfalseを返すとajaxが終了するというもの。これはリクエストを投げる直前で最終の入力値のチェックに使えます。クライアント側で止めれる最後のポイントです。

くるくる回るやつ出したい

上述のbeforeSendパラメータを使います。 送信ボタン等のHTMLをローディングgifに置換します。

Ajaxload - Ajax loading gif generator

このサイトすごい便利です。いろんなタイプのローディングgifを選べるし背景透過のもの作れます。

逆に、全ての通信を終えたあとのコールバック関数をcompleteパラメータで設定できます。この関数内で、ローディングgifを元のHTMLに戻してやります。

その他のあれこれ

  • cacheパラメータはfalseにしておく
    • IEではキャッシュが有効になってしまう
  • timeoutパラメータは設定しておく

$.ajaxにはたくさんの設定項目があるので徐々に使えるようになりたいです。