mod_access_tokenをPHPから使ってみる

mod_access_tokenとは

ウェブサイト上の画像やファイルに有効期限を指定して、ユーザーに一時的なダウンロードを許可する、ライブドアで独自開発したApacheモジュールです。このモジュールをApache Webサーバに組み込むことにより、画像やファイルをウェブ上で公開するときに有効期限をつけることができるようになり、Webアプリケーションと組み合わせる事で公開範囲の制御を行なう事が可能になります。


ソースコードはこちらから入手できます。
modaccesstoken - Secure downloading module for Apache2 - Google Project Hosting


livedoor ラボ「EDGE」 開発日誌 : 「mod_access_token」の配布開始と「EDGE src」公開のお知らせ - livedoor Blog(ブログ)


ファイルのアクセス制限をApacheのモジュールに任せてしまおうというものだ。
使いどころなどは、ファイルのアクセス制限にいちいちプログラムによる制御を入れられない大規模サイトなどが考えられる。
例えば、会員制サイトのログインユーザのみに見せるプロフィール画像など。


もしくは、コンテンツ自体に課金させるなどのサイトの場合にも使えそうだ。
モバイルサイトの場合、コンテンツに有料課金や、会員onlyの制限をしているものも多くあるので、使いどころがあるかもしれない。

mod_access_tokenのインストール

まずは、入れてみないと話にならないので、入れてみる。

$ wget http://modaccesstoken.googlecode.com/files/mod_access_token-0.10.tar.gz
$ tar xzvf mod_access_token-0.10.tar.gz
$ cd mod_access_token/
$ ls
Makefile.in  README  configure  configure.in  eg  mod_access_token.c
$ ls eg/
httpd.conf-example  sign.pl
$ ./configure
$ sudo make install


参考にしたサイトでは、エラーが起きたという話もあったが、得にエラーもなくインストールできた。
まめ畑


ちなみにインストールした環境はソースインストールしたApache2.0.59だ。

$ /usr/local/apache2/bin/httpd -v
Server version: Apache/2.0.59
Server built:   May 29 2008 14:44:08


httpd.confに以下のようなモジュールロードが書き込まれていた。

LoadModule access_token_module modules/mod_access_token.so


さっそく、適当な ディレクティブ に、サンプルの ディレクティブ を記述してテストをしてみる。

<VirtualHost xxx.xxx.xxx.xxx>
   #略
   <Location /dl>
       AccessTokenCheck On
       AccessTokenSecret 15cfb576a8bdc1551219fdeb3117ed85
       AccessTokenAccessKey 7864ffcb01fb5cde1f1c2f37b619fbcd
   </Location>
</VirtualHost>


これで、この指定した VirtualHostのdlディレクトリ内に直接アクセスすると 403のエラーが発生するようになる。

制限ファイルに、phpスクリプト経由でアクセスしてみる

まず、サンプルのファイルを作成しておく。

$ cd /path/to/document_root/
$ echo 'hoge' > dl/hoge.html

dlディレクトリ内にアクセス制限をした hoge.htmlというファイルがあるという想定だ。
もちろん、画像や動画であっても同じです。



次にphpファイルのソース。

<?php
$uri = '/dl/hoge.html';
$accesskey = '7864ffcb01fb5cde1f1c2f37b619fbcd';
$secret = '15cfb576a8bdc1551219fdeb3117ed85';
$expires = time() + 10; //制限は10秒
$plaintext = 'GET'.$uri.$expires.$accesskey;
$signature = base64_encode(hash_hmac('sha1', $plaintext, $secret, true));
$signature = str_replace('=', '', $signature);
$token_param = sprintf('AccessKey=%s&Expires=%s&Signature=%s'
   , $accesskey
   , $expires
   , urlencode($signature))
?>
<html>
<head>
<title>mod_access_token test</title>
</head>
<body>
mod_access_token test<hr>
plaintext:<?php echo $plaintext ?><br>
signature:<?php echo $signature ?><br>
<a href="<?php echo $uri ?>?<?php echo $token_param ?>">hoge.html</a>
</body>
</html>


それぞれ軽く説明。

  • $uri は、アクセスする対象のファイル /から始まる絶対パスである必要があります
  • $accesskey は、httpd.confで設定した AccessTokenAccessKeyと同じ値
  • $secret は、httpd.confで設定した AccessTokenSecretと同じ値
  • $example には、現在の Unix タイムスタンプ + 秒数を指定
  • $plaintext の、'GET' は、HTTPリクエスト時のメソッドです、今回は普通にaタグからのアクセスなのでGETとします
  • Signatureは、urlencodeしても、しなくても正常に動きましたが、base64_encodeした値に、スラッシュやプラス記号があったので、urlencodeしてみました


軽くつまづいた点

  • hash_hmacの第4引数に true を指定する必要がある。デフォルトで false でした。
  • base64_encode 後の文字列で、末尾の = が不要
    • 今回は無理やりstr_replaceしていますが、もっといい方法があるのかな?

mod_access_token導入時の確認方法

動いているか

直でアクセスして 403 Forbidden になるか、また、Apacheのエラーログにログが残っているか。

パラメータが正しいかどうか

アクセスしたパラメータが正しいはずなのにアクセス出来ないという場合には、エラーログを調べることで確認することができます。

エラーログには、$plaintext と、アクセスされたSignature と、想定する正しいSignatureが記録されています。

Invalid signature: <$plaintext> => <正しいSignature>:<アクセスされたSignature>

以下のような感じのログ

[Mon Mar 02 21:47:35 2009] [error] [client xxx.xxx.xxx.xxx] Invalid signature: GET/dl/hoge.html12359980617864ffcb01fb5cde1f1c2f37b619fbcd => 3s5CSeQ4gCJ+eu9AOf8KB+sB6hA:3s5CSeQ4gCJ+eu9AOf8KB+sB6hA=, referer:http://example.com/

このログでは、base64_encode後の末尾の = が付いているためエラーになっています。

.htaccessでの記述

LoadModule出きるぐらいであれば、httpd.confに記述することも可能かと思いますが、軽い案件で、.htaccessに記述したい場合に可能かどうか試してみました。


結論は、記述可能。ただし、httpd.confでの設定の上書きは出来ない。というものでした。


確実に保護したいものは、httpd.confで記述した方がよく、.htaccessで、以下のような記述をしても制限が外れることはありませんでした。

AccessTokenCheck Off


また、AccessTokenSecret、AccessTokenAccessKeyを別の値に上書きも、もちろん出来ませんでした。


逆に、サーバ管理チームと、アプリ作成チームがはっきり分かれていて、アプリの都合でのhttpd.confへの記入するより.htaccessの方が運用に合っている場合や、アプリのバージョン管理でSVNなどで制限情報も管理したい場合には、.htaccessというのもありではないでしょうか?

$ cat .htaccess
AccessTokenCheck On
AccessTokenSecret 15cfb576a8bdc1551219fdeb3117ed85
AccessTokenAccessKey 7864ffcb01fb5cde1f1c2f37b619fbcd

使いどころを考えてみる

軽く思いついたのは、以下のようなケースとか。


ダウンロードサーバが別。または、ASPっぽいサービスとか。

  • サーバ間通信で、コンテンツダウンロードタグを発行する
  • Webアプリのダウンロードページで、そのダウンロードタグを表示し、ユーザにコンテンツをダウンロードしてもらう
  • 有効期限が切れたり、直アクセスは禁止

なんか、こんなASPサービスもありましたが、比較的簡単に、しかもダウンロードサーバは軽くできそう。


あとは、モバイルサイトのコンテンツ周りでしょうか。現状モバイルサイトを作成し、特定ユーザのみにDLしてもらうコンテンツの場合には、コンテンツファイル自体をPHPで吐き出すようなことをしています。
PHPでコンテンツを吐き出すため、オーバーヘッドがありますし、ダウンロードページ表示時の会員認証と、そこからのコンテンツダウンロード時の会員認証など、二重にチェックになっている部分があるので、そこは軽くできるかもしれません。


あとは、携帯の場合、ダウンロードページを画面メモをしていて、そこからのダウンロードを阻止したい場合にも有効かもしれません。


期間限定の例えば○月○日までDL可能なコンテンツという場合には、EXPIRESが固定になりますので、静的なダウンロードページに静的なリンクにしてしまうという方法もありますね。

要検証な点

  • 403時にhtmlが表示できるか
    • ErrorDocument 403 とか効くのかな?
  • docomoのiモーションダウンロード時の挙動
    • Rangeヘッダによる分割アクセス時
    • 403になった場合の端末挙動
  • auのobjectダウンロード時の挙動
    • Rangeヘッダによる分割アクセス時
    • 403になった場合の端末挙動
  • auのダウンロードCGIの挙動
  • SoftBank
    • 公式サイトのほげほげしたダウンロードの場合の挙動


ほかに何かあるかなぁ?

ついでに

Expires: 有効期限 UNIX エポックからの秒数

Unixタイムスタンプのことを、UNIX エポックと呼ぶのを始めて知った

The Unix epoch is the time 00:00:00 UTC on January 1, 1970.

http://en.wikipedia.org/wiki/Unix_time