音楽ファイルや動画ファイルの取得にも認証を導入したときにぶつかった問題
もう既に2年近く前、Android 2.3がまだ新しいと言われていた頃の話だが、当時は相当苦しんだので、検証記録として残してみる。
はじめに
とあるWebアプリケーションの開発を担当している人から、「Androidの実機だと音が再生されない。どうにも原因が分からないので助けて欲しい」という相談を受け、その辺の知識もほとんどないままにヘルプすることになった。
話を聞いてみると、以下の状況ということが分かった。
下準備
せっかくだからということで、今回、audioタグとvideoタグ、比較としてimgタグを使った検証をしようとテストプログラムを作っていたら、動画に関しては生半可な対応では再生されなかったので(どうやらHTTP 206(Partial Content)を使用するらしい)、Apache httpdのxsendfileモジュールを使用した。
導入に関しては、ここでの説明は手抜きするが、以下のサイトを参考にした。
ざっくり書くと、以下のような感じ。(CentOS 6.4での実行例)
# yum groupinstall "Development Tools" # yum install httpd httpd-devel # curl -o mod_xsendfile.c https://tn123.org/mod_xsendfile/mod_xsendfile.c # apxs -cia mod_xsendfile-0.12/mod_xsendfile.c # vi /etc/httpd/conf.d/xsendfile.conf <IfModule mod_xsendfile.c> XsendFile on XsendFilePath /var/www/html </IfModule> # service httpd configtest # service httpd restart
認証無しのケースでの確認
「そもそも再生されない」っていうことだと困るので、まずは以下のプログラムで試す。
<?php $user = '-'; $time = time(); $msg = array(); $msg[] = "HTML5Test[{$_SERVER['REQUEST_METHOD']}]"; $msg[] = "\"{$user}\""; if (isset($_GET['file'], $_GET['type'])) { $size = filesize($_GET['file']); header("Content-Length: {$size}"); header("Content-Type: {$_GET['type']}"); header("X-Sendfile: {$_GET['file']}"); $msg[] = "\"file={$_GET['file']}&type={$_GET['type']}\""; } else { $msg[] = "\"file=view&type=text/html\""; ?> <div> <img src="?file=image1.png&type=image/png&time=<?php echo $time;?>" /> </div> <div> <audio src="?file=audio1.mp3&type=audio/mpeg&time=<?php echo $time;?>" autoplay="autoplay" controls="controls"> audio not supported </audio> </div> <div> <video src="?file=video1.mp4&type=video/mp4&time=<?php echo $time;?>" autoplay="autoplay" controls="controls" width="320" height="240"> video not supported </video> </div> <?php } $msg[] = "\"{$_SERVER['HTTP_USER_AGENT']}\""; $msg[] = "\"{$_SERVER['REQUEST_URI']}\""; trigger_error(implode(' ', $msg), E_USER_NOTICE); ?>
timeパラメータは、一応のキャッシュ対策。
これで、以下のケースを試してみる。
- PCのGoogle Chrome
- Android 2.3(Galaxy S)の標準ブラウザ
- Android 4.2(Galaxy S4)の標準ブラウザ
実際にアクセスすると、Apache httpdのエラーログに以下のようなログが記録される。(PCのGoogle Chromeでの出力例)
[Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=view&type=text/html" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/ [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=image1.png&type=image/png" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=image1.png&type=image/png&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=audio1.mp3&type=audio/mpeg" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=audio1.mp3&type=audio/mpeg&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=video1.mp4&type=video/mp4&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=video1.mp4&type=video/mp4&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php [Fri Dec 06 14:35:56 2013] [error] [client 192.168.0.121] PHP Notice: HTML5Test[GET] "-" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-noauth.php?file=video1.mp4&type=video/mp4&time=1386308156" in /var/www/html/test-noauth.php on line 43, referer: http://192.168.201.200/test-noauth.php
ちなみに、書いてあるIPアドレスはすべてプライベートアドレスなので、アタックしようとしても無駄よん、念のため(謎)。
オレオレ認証処理を追加して検証
今回、認証機構を作るのが目的ではないので、ユーザIDを入力してPOSTすると、それをセッションに保存しておき、そのデータの有無で認証済みかどうかを判定するという手抜きな仕組みで代用する。(PHPのセッションはクッキーに保存されたIDで識別する設定になっていて、今回の要件には合っているので)
<?php session_start(); if (isset($_POST['logout'])) { unset($_SESSION['authorized']); } if (isset($_POST['username'])) { $_SESSION['authorized'] = $_POST['username']; } $user = isset($_SESSION['authorized']) ? $_SESSION['authorized'] : '-'; $time = time(); $msg = array(); $msg[] = "HTML5Test[{$_SERVER['REQUEST_METHOD']}]"; $msg[] = "\"{$user}\""; if (isset($_SESSION['authorized'])) { if (isset($_GET['file'], $_GET['type'])) { $size = filesize($_GET['file']); header("Content-Length: {$size}"); header("Content-Type: {$_GET['type']}"); header("X-Sendfile: {$_GET['file']}"); $msg[] = "\"file={$_GET['file']}&type={$_GET['type']}\""; } else { $msg[] = "\"file=view&type=text/html\""; ?> <div> <img src="?file=image1.png&type=image/png&time=<?php echo $time;?>" /> </div> <div> <audio src="?file=audio1.mp3&type=audio/mpeg&time=<?php echo $time;?>" autoplay="autoplay" controls="controls"> audio not supported </audio> </div> <div> <video src="?file=video1.mp4&type=video/mp4&time=<?php echo $time;?>" autoplay="autoplay" controls="controls" width="320" height="240"> video not supported </video> </div> <hr /> <form action="" method="post"> <input type="submit" name="logout" value="Logout" /> </form> <?php } } else { $msg[] = "\"file=login&type=text/html\""; ?> <form action="" method="post"> <input type="text" name="username" /><br /> <input type="submit" value="Authz" /> </form> <?php } $msg[] = "\"{$_SERVER['HTTP_USER_AGENT']}\""; $msg[] = "\"{$_SERVER['REQUEST_URI']}\""; trigger_error(implode(' ', $msg), E_USER_NOTICE); ?>
認証無しのケースから変わったのは以下。
- 3〜10行目でオレオレ認証情報の取得などをしている。一応、ログアウト機能も実装。
- 17行目で認証済みかどうかの条件判定
- 46〜59行目でログアウトのためのフォームと認証のためのフォームを、それぞれの場合分けに従って表示。
これで先ほどと同じケースを試してみる。(今度は、すべてのケースでのログを、見やすいように余分な情報を除去して載せてみる)
HTML5Test[GET] "-" "file=login&type=text/html" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php" in /var/www/html/test-auth.php on line 65 HTML5Test[POST] "hhelibex" "file=view&type=text/html" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=image1.png&type=image/png" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=image1.png&type=image/png&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=audio1.mp3&type=audio/mpeg" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex" "file=video1.mp4&type=video/mp4" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308835" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php
HTML5Test[GET] "-" "file=login&type=text/html" "Mozilla/5.0 (Linux; U; Android 2.3.6; ja-jp; SC-02B Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" "/test-auth.php" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/ HTML5Test[POST] "hhelibex-2.3" "file=view&type=text/html" "Mozilla/5.0 (Linux; U; Android 2.3.6; ja-jp; SC-02B Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" "/test-auth.php" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex-2.3" "file=image1.png&type=image/png" "Mozilla/5.0 (Linux; U; Android 2.3.6; ja-jp; SC-02B Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" "/test-auth.php?file=image1.png&type=image/png&time=1386308864" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "-" "file=login&type=text/html" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386308864" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-2.3" "file=video1.mp4&type=video/mp4" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308864" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-2.3" "file=video1.mp4&type=video/mp4" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308864" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-2.3" "file=video1.mp4&type=video/mp4" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386308864" in /var/www/html/test-auth.php on line 65
- Android 4.2(Galaxy S4)の標準ブラウザ
HTML5Test[GET] "-" "file=login&type=text/html" "Mozilla/5.0 (Linux; Android 4.2.2; ja-jp; SC-04E Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Version/1.0 Chrome/18.0.1025.308 Mobile Safari/535.19" "/test-auth.php" in /var/www/html/test-auth.php on line 65 HTML5Test[POST] "hhelibex-4.2" "file=view&type=text/html" "Mozilla/5.0 (Linux; Android 4.2.2; ja-jp; SC-04E Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Version/1.0 Chrome/18.0.1025.308 Mobile Safari/535.19" "/test-auth.php" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex-4.2" "file=image1.png&type=image/png" "Mozilla/5.0 (Linux; Android 4.2.2; ja-jp; SC-04E Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Version/1.0 Chrome/18.0.1025.308 Mobile Safari/535.19" "/test-auth.php?file=image1.png&type=image/png&time=1386309043" in /var/www/html/test-auth.php on line 65, referer: http://192.168.201.200/test-auth.php HTML5Test[GET] "hhelibex-4.2" "file=audio1.mp3&type=audio/mpeg" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=audio1.mp3&type=audio/mpeg" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65 HTML5Test[GET] "hhelibex-4.2" "file=video1.mp4&type=video/mp4" "stagefright/1.2 (Linux;Android 4.2.2)" "/test-auth.php?file=video1.mp4&type=video/mp4&time=1386309043" in /var/www/html/test-auth.php on line 65
ログだけではちょっと分からないと思うが、以下のような動作をしている。
- PCのGoogle Chromeでは、音声、動画ともに自動再生される。
- Android 2.3では、音声は自動再生される(しようとする)が、動画は再生ボタンを押さないと再生されない。
- Android 4.2では、音声、動画ともに自動再生される。
- ただ、複数のメディアの同時再生はできないようで、音声再生中に動画の再生が始まると、音声の再生が一時停止するらしい。逆も同じ。
ちなみに、動画へのアクセスのログが多いのは、再生時に複数回に分けて分割して取得していたりするからだと思われる。
で、パッと見ただけでは分かりにくいかもしれないが、Android 2.3の音声ファイル取得のログが以下のようになっている。
HTML5Test[GET] "-" "file=login&type=text/html" "stagefright/1.1 (Linux;Android 2.3.6)" "/test-auth.php?file=audio1.mp3&type=audio/mpeg&time=1386308864" in /var/www/html/test-auth.php on line 65
まず、ユーザ名が取得できていないことから、セッション識別に必要なクッキーが送られていないと推測される。「file=login」と出力されていることからも、ベースとなるHTML文書の取得とセッションを共有できていないことが分かる。
と、それ以前に、UAが「stagefright/1.1」となっていて、HTMLファイルや画像ファイルとは異なるUAによってアクセスされていることも分かる。
Android 4.2では「stagefright/1.2」が使用され、ユーザ名もちゃんと取れていることから、Android 2.3におけるバグなのかな、と推測される(Android 3.xの端末がないのでそこの検証はできないが‥)