Webアプリケーションにおいて、例えば個人のアップロードした画像等のファイルは、自分自身もしくは権限を与えた人のみに制限したいということはよくあります。
権限のチェックなどをふつうに行えばよいのですが、そうなると大抵はDBやLDAPへのアクセスが必要になって、画像が沢山あるページなどを表示するときに、サーバリソースの心配をしなくてはなりません。
そこでサーバリソースに負荷をかけずに、適切なアクセス制御をする仕組みの例を作りました。
仕組み
静的コンテンツのアクセス制御をトークンベースで行います。
トークンは、
有効期限$Base64(HmacSHA256(sessionId$リソースパス$有効期限))
として作ります。このサンプルでは
<body>
<img src="/images/secret.png?token=<%= URLEncoder.encode(TokenUtil.createToken(request, "/images/secret.png"), "UTF-8")%>"/>
</body>
のようにTokenUtilを介して上記のようなルールでトークンを生成しています。実務で使うにはスクリプトレットはアレなので、イメージタグ用のtaglibを作った方がよいでしょう。
そして、ブラウザからトークン付きで画像のリクエストがとんでくる訳ですが、これをフィルタでトークンチェックします。
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
if (!TokenUtil.validToken((HttpServletRequest) servletRequest)) {
httpResponse.sendError(403);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
TokenUtil.validTokenでは、
- トークンがある。
- セッションがある(認証)。
- 送られてきたトークンの有効期限より現在日時が小さい。
- ↑で作られたトークンを同じ手順でトークンを作り、送られてきたものと一致する。
の順にトークンが検証されて、全部パスすれば画像が表示されるようになっています。
トークンに含まれる平文部分の有効期限を改ざんしても、HmacSHA256のところでハッシュ値が一致しないのでエラーにすることができます。 同様にこのトークンをもって、別の画像を表示しようとしても、やはりHmacSHA256のハッシュ値が一致しないのでエラーになります。
性能検証
さてこのフィルタあり/なしで性能差があるか、軽く検証してみました。
フィルタ | 平均(ms) | 最大(ms) | 90%ライン (ms) | スループット |
---|---|---|---|---|
あり | 91 | 829 | 116 | 471.7 |
なし | 90 | 613 | 120 | 463.7 |
特に差は出ないようです。