WIZ-CODE.blogMENUWIZ-CODE.blogについてWIZ-CODE.blogJavaScriptやAjaxをテーマとしたブログです。2017/08/30前回(低コストでできるだけセキュアなAjaxパスワード認証を考える)の続きです。Ajaxによるパスワード認証をコストをできる限りかけずに実装するという取り組み(プロジェクト)を紹介しています。おさらいですが、まずパスワード認証を実装するにあたって、パスワードの安全な受け渡し、つまり暗号化をどうするかという問題がありました。これについては自前で通信の暗号化をはかるより、一定の信頼性の得られるSSL、それも徐々に浸透しつつある格安SSLを利用して導入するという結論になりました。格安SSLの多くは、信頼性の低いドメイン認証ですが、小規模でECサイトのような取引などの生じないサイトであれば十分と考えられます。なによりも、SSLの導入によって、中間者攻撃によるWebサイトの改ざんと成りすましを防ぐことができ、単純な暗号化通信では対策できない問題が解決されます。ソースが丸見えになるクライアントサイドで、暗号化スクリプトをあえて実行する必要がなくなるので、脆弱性が緩和されます。次にCSRFやXSSなどのスクリプティング対策ですが、トークンを使った対策やユーザーから受け取った(信頼するべきでない)データの無害化するといった基本的な対策が、Ajaxを用いた認証でもそのまま使えること、また、Ajax自体にクロスサイトの攻撃を抑制する機構がそなわっていることを確認しました。その他にパスワードの保管方法について、これは徳丸本に書かれていた方法そのものですが、ソルトとストレッチングを用いて暗号化して保存することとしました。平文での保管は言うまでもないですが、パスワードを単純にハッシュ化しただけでは脆弱であるからです。今回の焦点となるのが、Ajaxによるパスワード認証の実装です。上記の三点のセキュリティ課題を具体的にどのように実装に落とし込むかという点と、Ajaxでどのようにサーバー・クライアント間でデータのやり取りを行い、また非同期のページ遷移をどう行っていくかを解説します。実はすでに実装例を作成済みであるので、先にリンクを張って置きます。ドメインがこのブログのものと違いますが、それはこのサイトのレンタルサーバーの共有SSLを使用しているためです。⇒Ajaxログイン認証のサンプルページこのサンプルページでは、ユーザーIDが「test_user1」、メールアドレスが「test_user1@test.com」、パスワードが「testUSER1」のアカウントを登録しているので試しにログインしてみてください。ログイン認証の手順ログイン/登録ページ「https://digick-wiz-code.ssl-lolipop.jp/dev/test/ajax-login/」へアクセスする。「ログインする」ボタンを押して次の画面へEメールとパスワードのそれぞれのフォームに、test_user1@test.com、testUSER1と入力して「サインイン」をクリックする。test_user1さん!」というメッセージに変わり、メニューが出現する。現在(11/4時点)ではログアウト機能のみ実装。本来サンプルページなので「チャット」などは未実装のまま。「設定」画面はUIのみうわべだけ実装済み。メールアドレスの変更などの実際の機能は今後実装する予定。ログイン状態でリロードしても、最初のログイン/登録ページは表示されずユーザーのホーム画面(メニュー画面)が表示される。メニュー画面で「ログアウトする」をクリックすると、ログアウトの確認ダイアログが表示される。ここで「ログアウトする」をクリックするとサーバー側でセッションデータの破棄とセッションの終了が実行され、クライアント側のクッキーも破棄され、最初の画面にリダイレクトされる。これらのデモで使用されているソースコードを以下のページで公開しています。ログイン機能とパスワード認証まわりの実装はほぼ9割方完成しています。公開ソースコードこのプロジェクトはサーバープログラミング言語にPHPを使用しています。そのため、ソースコードの解説はPHPコードに限定されますのでご了承ください。Webセキュリティに配慮した実装SSLの実装に関しては、HTTPS環境が用意できれば、サイト管理側はHTTPS用のリンクを用意するだけなので、特に気をつけることはないと思われます。ユーザーがHTTPSではなく、HTTPで接続してきた場合を想定する必要があるでしょう。この対処法はWeb上で多く取り上げられているので詳述しませんが、.htaccessを設定するかサーバー側で判別して対処します。自分は.htaccessを編集して、HTTP接続できたユーザーをHTTPSにリダイレクトするようにしています。次にCSRF対策についてですが、主要な対策は正式なサイトからのリクエストであることを判別するためトークンによる確認方法をとります。サイトに予測困難な乱数のトークンを仕込んでおき、そのトークンを送信してきたユーザーのリクエストにしか応答しないというものです。徳丸本によると、トークンはリクエスト毎に異なるものを用意する必要はないとのことなので、このプロジェクトではCSRF対策トークンにセッションIDを使用しています。die();}このようになっていて、$login_tokenはリクエストデータから取得し、それをセッションIDと突き合せます。照合が失敗したら不正なアクセスと見なして接続を切ります。jQueryやPrototype.jsなどのAjaxライブラリがXMLHttpRequestを行うとき、HTTP_X_REQUESTED_WITHリクエストヘッダにXMLHttpRequestという値を付けて送ってくるので、サーバー側でそれをチェックすることでクロスドメインのリクエストでないことを確認します。die();}ただし、これらのリクエストヘッダを攻撃者が偽装・改ざんすることが可能だそうなので、あくまで保険的な位置づけとなりますが。XSS対策の場合、様々な攻撃手段があるため対策は多様ですが、ここでは基本となる対策を重点的に行います。ブラウザへ出力するデータはhtmlspecialchars()によって無害化する。htmlspecialchars()はむやみに使わない。パスワードなどデータベースに保存するなら入力値はそのまま保存。このメソッドを使用するのは基本的にブラウザに出力する時のみ。ユーザーからの入力データはバリデーションを行うレスポンスヘッダで文字エンコーディング指定(UTF-8)AjaxレスポンスをHTMLとしてJavaScriptで流し込むときは、JavaScriptでスクリプト除去を行う。Prototype.jsならばString.stripScripts()、jQueryならばjQuery.parseHTML()に通す。その他、セキュリティ上、攻撃の標的にされやすいセッション管理機構について対策します。Webアプリケーションでは認証の記録だけでなく、ユーザーの識別にセッション機能を使用します。セッションが必要なのはHTTPが状態を引き継がない仕様だからです。単一ページのアプリならば、パスワード認証さえすませればセッション機能などむしろ不要です。すべてのアプリは複数のページから構成されています。ユーザーが異なるページを訪れたとき、新たなページではそのユーザーについて何の情報も持っていません。セッション管理機構はサーバー側とクライアント側に同じ識別データを保管し、それによってユーザーを識別します。ユーザー(クライアント)側にデータを残すため、大抵のWebアプリケーションはCookieを使用します。URLに直接識別データを埋め込む方法がありますが、こちらはセキュリティリスクが問題になっているので、現実的な方法ではありません。Cookieは昔から脆弱性が指摘され、危険視されてきたブラウザ機能です。セッション機能自体もセッションハイジャックやフィクセーションといった攻撃にさらされやすく、双方に厳重な対策が求められています。このプロジェクトでは、主に徳丸本を参考にしたセッション・セキュリティ対策を行います。ほんとうはセッションIDの生成方法の改善までやりたかったのですが、自分のサイトが設定自由度の低いレンタルサーバーのため断念しました。その他に実施するセキュリティ対策は次のようになります。セッションIDをURL埋め込みにしない。CookieにHttpOnly属性とSecure属性を付けて送信する。ログインやユーザー登録後にsession_regenerate_id(TRUE)関数でセッションIDを振りなおす。session_regenerate_id(TRUE)関数はログインとユーザー登録に関連するregister.phpとlogin.phpで使用しています。HttpOnlyとSecure属性はPHPファイルの初めの部分で、session_start関数が呼ばれる前にsession_set_cookie_params関数で指定しておきます。環境が許すならphp.iniに設定する方法もあるでしょう。セッションの有効期限についてはブラウザを閉じるまでとするのが主流とのことです。TRUE);となります。TRUE);ここでAjaxを使用するページにセッションが必要か、という疑問があります。Ajaxアプリケーションはページ遷移のない単一のページで動作するからです。セッションでユーザーを識別できる点はCookieを使っても同じです。確かにセッション機構はAjaxログイン認証を実現するのになくてもよいものです。セッションデータは基本的に外部からアクセスできず、改ざんなどの心配がないためCookieよりセキュリティの面で勝ります。その上、データベースにアクセスする負荷を軽減したり、セッションIDをCSRF対策のトークン代わりにできるなど利点が多いです。このプロジェクトはセッションを利用しています。パスワードの保管方法は、ソルトとストレッチングを使ったハッシュ化を徳丸本の解説に沿ったやり方で実装しています。PHP5.5以上では、手軽にして強力なパスワードハッシュ化関数であるpassword_hash()とpassword_verify()が追加されているので、そちらを使用した方がより安全な対策になると思います。自分のローカル/リモート環境のPHPがバージョン5.3であるため、それらの関数が使えません。ここまでプロジェクトのセキュリティ対策を簡単に追ってきました。次に本題に移りたいと思います。Ajaxによる完結したパスワード認証ページの作り方です。Ajaxによるアプリケーションの実装方法Ajaxを用いたWebアプリケーションの例にTwitterがあります。モバイルアプリではなくPCでTwitterを閲覧すると、画面遷移のときに画面が真っ白になることなく、動的にスムーズに入れ替わることに気づくかと思います。Firebugなどの開発ツールを使用すれば、この画面遷移が通常のHTTPリクエストではなく、XMLHttpRequestを使っていることがわかるはずです。ちなみにTwitterの認証はもっと厳重な方式を用いていると思いますが、ここではセキュリティの話は置いておき、実際にTwitterのようなAjaxアプリケーションを実現させる方法を考えてみたいと思います。このプロジェクトでは、最初の画面(ログインかユーザー登録を求める画面)からパスワード認証後に会員ページへ飛ぶ工程までを実装します。Ajax通信でデータのやり取りを完結させるため、画面遷移はJavaScriptで制御しなければなりません。サーバー側で処理するならばページごとに○△□.phpというファイルを用意すればいいのですが、JavaScriptで制御する場合、ずっと同じページにいるわけですからそういうわけにいきません。具体的にどうするのかというと、従来サーバー側でページ単位でファイルに分けていたところを、JavaScriptでそれぞれ「状態」として保持させます。最初の画面(ログインかユーザー登録を求める画面)を「状態A」として、ログインのフォームが表示される部分を「状態B」とします。状態ごとにJavaScriptで適切に画面構築をしていくわけです。すべてJavaScriptで動的にHTMLを生成するのは危険です。HTML部分はやはりサーバーからロードしたデータを使用します。ある画面から他の画面に遷移するたびに、Ajax通信を使ってHTML断片などのデータを引っ張ってくるのではサーバーに負荷がかかります。そこで、ロードするHTMLを状態ごとではなく、ある程度まとめたものとしてロードするようにします。「最初の画面」に使うHTMLと、ログインフォーム画面のHTML、そしてユーザー登録フォーム画面のHTMLをセットにしたものをサーバーで用意します。それとは別に「会員メニュー画面」や「ログアウト画面」など認証後の画面をまとめたHTMLを用意します。それによって、Ajax通信でHTMLをロードする回数は2回で済むようになります。</div></div>上のコードは上記の「Ajax認証デモページ」で使用されているHTMLデータで、認証前に使用するすべて画面をひとつにまとめたものです。これは、「Ajax認証デモページ」を最初に訪れたとき、ロードされます。これを実際に表示すると、「最初の画面」と「ログイン画面」と「ユーザー登録画面」が縦に並んだおかしな画面になります。本当のところはコンポーネント単位で要素に「hidden」というクラス名が記述してあり、最初はすべて非表示で始まります。ページロードが完了すると、JavaScriptで「最初の画面」コンポーネントだけを表示させます。各手続きが済むごとに、表示中のコンポーネントを消して、次の(下の)コンポーネントを表示するわけです。前述のようにログイン認証前は、認証後に表示するHTMLデータセットがロードされないよう分けているので、セキュリティに配慮されています。各画面をコンポーネントと呼ぶことにします。コンポーネントは前述の「状態」にあたります。この「Ajax認証デモページ」でもそうですが、これらの各状態をJavaScriptで適切に管理していくことが、Ajax認証システムの肝になってきます。状態を管理するとはどういうことでしょうか。UMLモデリング理論にステートマシン図という概念があります。Webアプリケーションの設計をする上で、ひとつのモデルになる考え方です。以前JavaScriptでステートマシンを実装するためのソースコードを紹介したことがあります。このプロジェクトでは、そのソースコードを機能追加した上でステートマシンの実行に使用しています。Async-FSMのソースコードをGitHubで公開しました。導入方法や使い方の説明も同ページに記載しています。Libraryこのソースコードの解説までしようと長くなるので省きますが、JavaScriptで各状態を制御するシステムを作るには、こうしたライブラリなりフレームワークが必要になってくると思われます。ステートマシン図はそれぞれ機能を持った状態(State)とそれらを結ぶ遷移(Transition)で記述され、状態から状態への遷移は、トリガ(Trigger)というイベント(事象)をきっかけとします。FSM-0.6.jsはそれらStateとTransitionをオブジェクト(実体)として扱えるようにしており、、またTriggerをメソッドとして実装し、ステートマシン図の設計通りにWebアプリケーションを動かせるよう工夫しています。基本的な書き方は次のようにします。fsm.start();このプロジェクトのソースコードの詳細は前述のリンクにて確認できます。SyntaxHilighterを全コードに適用しているため、少し重いかもしれません。Ajaxでアプリケーションを構築するのに、ステートマシン図を念頭に置いた実装方法を説明しました。記事が長くなったので、具体的なソースコードの解説等はまたの機会にいたします。test_user1さん!」というメッセージに変わり、メニューが出現する。PMk.tさん返信が遅れてすみません。当のデモページのソースコードはhttp://wiz-code.digick.jp/ajax/ajax-login-demo.htmlのページで確認できます。認証後のメインページのソースはadmin-frag.inc.phpにあります。これはPHPファイルではありますが、中身はただのHTMLソースです。これがHTMLファイルやPHPファイルである必要はなくHTMLソースの書かれたテキストファイルであれば***.inc.txtのようなファイルでも問題ないです。本格的にAjax認証によるアプリケーションを作成するならば、PHPのテンプレートエンジンやフレームワークを利用した方がいいかもしれません。コメントをキャンセルメールアドレスが公開されることはありません。*サイト次回のコメントで使用するためブラウザーに自分の名前、メールアドレス、サイトを保存する。関連記事2012年を前に......ひさびさの更新です。今年も残すところ数日ですが、いろいろなことがありました。...ホームページを一部リニューアルしました一年近く更新がなく、ご無沙汰しておりました。...JavaScriptでクイズやADVゲームを作ろう久々にサイトを更新しました。ADVゲームのサンプル。...そして再びRPG制作へ......突然ですが約1年半ぶりにRPG制作を開始します。...Ajaxでパスワード認証の続き-公開スクリプトこの記事は過去のセキュリティ的に低い観点で書かれたものです。...JavaScriptで作ったオープニング風デモ画面JavaScriptで某鉄道会社のCMっぽいデモ画面を作ってみました。...フライトシミュレーションを再アップ以前公開したフライトシミュレーションの制御部分が完成したので再アップします。前回紹介した宇宙フライトシミュレーションに、いくつかの機能を追加しました。音楽プレイヤーカテゴリーJavaScript/AjaxPHPTipsゲームブラウザアーカイブ2018年7月2017年7月2016年12月2016年11月2016年10月2016年3月2016年1月2015年12月2015年11月2015年10月2015年9月2015年8月2014年7月2014年6月2014年4月2014年2月2014年1月2013年12月2013年10月2013年8月2013年6月2013年4月2013年3月2013年1月2012年11月2012年8月2012年6月2012年5月2012年4月2012年3月2011年12月2011年10月2011年9月2011年8月2011年7月2011年6月2011年5月2011年4月2011年3月2011年2月2011年1月2010年12月2010年11月2010年10月2010年9月2010年6月2010年5月2010年4月2010年2月2010年1月2009年12月2009年11月2009年10月Ajaxでセッション(状態)管理する方法JavaScriptやAjaxをテーマとしたブログです。