Chapter 7
セキュリティと認証・アクセス権
この章が目指す最も重要な目標は、作成するアプリケーションのセキュリティを確保するということです。もちろん、手軽にできるのであれば長々と説明する必要はありません。しかしながら、INTER-Mediator自身のさまざまな設定に加えて、INTER-Mediator外のデータベースやサーバーOSといった部分にも注意が必要です。この章では、全体的なセキュリティ設定に加えて、データベースエンジンで確保すべき動作と、INTER-Mediatorのソリューションに対する設定などを説明します。
7-1Webアプリケーションセキュリティの前提
INTER-Mediatorによるシステムを作成した場合の、サーバーやクライアントにおいて、何を前提としてセキュリティ設計するべきかをまず最初にまとめておきます。
INTER-Mediatorを稼働するサーバーの前提条件
INTER-Mediatorで作成したソリューションを稼働するには、原則として何らかのサーバーが必要です。すでにこれまでに紹介している通り、VMでの稼働も可能ですが、サーバー機での運用、クラウドサービス上のインスタンス、VPS、レンタルサーバーなど、さまざまな形態で稼働させることができます。ここでのセキュリティ上の原則は、「管理者以外はログインできない」そして「サーバーは健全に稼働している」ということです。
管理者であれば、ログインできて、ファイルの内容を参照したり、ファイルを書き換えたりができますが、そうでないユーザーはログインができないということが大原則になります。この原則が守られていて、サーバーが意図した通りに稼働していれば、PHPのファイル、つまり定義ファイルや、あるいは別の設定ファイルなどにデータベース利用のための「パスワードを記述する」ということをしても、パスワード自体は外部に漏れません。
また、よくあるトラブルで、PHPが稼働していないときにWebブラウザーから.phpファイルを開くと、PHPのプログラムが丸見えになるようなことがあります。この時、もちろん、プログラム自体に何かのパスワードが書かれていれば、見えてしまい、パスワードの漏洩が発生します。しかしながら、これは意図した動作ではありません。そうならないように稼働させるのが管理者の役割であり、あるいはプロバイダの役割でもあります。「Webサーバーは生きているのにPHPだけ落ちる可能性がある」という指摘もあるかもしれませんが、問題がある状況を前提にするのは、かなり信頼性が疑われるシステムであり、このようなシステムは利用するべきではありません。通常の「稼働していることが前提」のシステムは、問題があればWebサーバーを停止させるのが原則であり、言い換えれば、問題があれば一切の機能を落とすくらいの作業をしなければ、「PHPのソースが見える」だけでなく、さまざまな悪影響が出てしまう可能性があります。これは、サーバー運用のポイントでもあります。
結果的には、サーバーでは、管理者が意図したサービスのみを、管理者以外のクライアントにサービスをする状態にするのが原則です。例えば、WebサーバーでTLS(一般にはSSLと言われますが、この章ではSSLの後継規格である「TLS」を用います)でしか接続させていないというのであれば、そのサーバーはネットワークから見ると、443番ポートしか開いていないというのが原則です。管理者がそのようにセットアップをして、第三者による変更を許さないようサーバー管理者が責任を持つ、という前提条件でWebアプリケーションは成り立っています。
INTER-Mediatorを利用するネットワークの前提条件
Webアプリケーションなので、ネットワーク上に存在しているということは当然なのですが、業務系のアプリケーションの場合、文字通りの意味で世界中のネットワークを通るわけではなく、場合によっては、組織内のクローズドなネットワークだけを通してのアプリケーションの利用に限られていることもあります。状況に応じて脅威は変化し、対策も変わってきます。
しかしながら、アプリケーションの開発者や利用者にコントロールできるのは、TLSの利用をするかどうかという点が一番大きいでしょう。また、アプリケーションによっては認証の利用やアクセス権(認可)の設定が必要かどうかを検討するかもしれません。認証やアクセス権については、この章の大きな目的であって各所で説明があるので、ここではTLSについての基本的なスタンスを説明します。
執筆時点で最新規格であるTLS 1.2は、適切な暗号スイートを利用すれば、送信者と受信者以外に通信内容を取り出せないように暗号化通信を実現できます。暗号を解読するには極めて長い時間を要することから、将来致命的な弱点が見つからない限り、現実的な時間での解読はほぼ困難と言えるでしょう。なお、SSLについては攻撃手段が発見されて、通信の傍受の可能性は0ではなくなったので、利用する仕組みとしては現在は既に除外されています。
TLSで守られるとしたら、どういう状況でしょうか? まず、クライアントとサーバーでは、通信前後に暗号化が解除される段階があるので、TLSによって、すべてが守られるわけではありません。一方、Wi-Fiの電波を解析したり、Ethernetに流れる通信結果を取り出しても前述の通り解析は事実上不可能ですので、漏洩はTLSを使う限り原則的には発生しません。通信内容を読み取られたとしても通信経路上のデータは暗号化されているからです。また、サーバーとクライアントの間には、たくさんのルーティングの機材(ルータあるいはホスト)が介在しますので、その機材を通る間のパケットを傍受される可能性もあります。しかし、TLSの場合は、中間で暗号化の解除は通常はできませんので、この脅威も取り除かれることになります。
では、組織内のネットワークしか通過しないのなら、TLSは必要でしょうか? これは、組織内のネットワークが正しく運用されているかと、組織内のスタッフを信用できるかどうかに関わってきます。「社内だから大丈夫」と思ったら、Wi-Fiに誰もが入れるようなお粗末な管理だと、「内部ネットワーク」とは言えず、結果的にはインターネットカフェなどと同じ程度の安全性になります。つまり、組織内だからTLSは不要であるという結論を出すには、ネットワークの管理が正しく行われているかどうかに依存します。その上で、スタッフを信頼できるかどうかは、是非とも組織内でディスカッションをしてみるべきテーマです。
ブラウザーのセキュリティ
現在、セキュリティ上の懸念点があるのは、サーバーやネットワークよりもむしろブラウザーであると言えるでしょう。ソフトウェアは作り方によってはセキュリティ上の問題点を発生します。特にXSS(クロスサイトスクリプティング)については、あらゆるWebアプリケーション開発者が常に意識すべき問題です。これについては、このすぐ後の『クロスサイトスクリプティング攻撃(XSS)とinnerHTML』で説明します。
ブラウザーでの一番の問題は、利用者が切り替わる場合の対処です。認証が必要なサイトをあるユーザーが利用していたとして、しばらくデスクを空けていたとします。その間に、画面がスリープやロックになって、元のユーザーにしか分からないパスワードでしか解除できない状況なら問題はありません。しかし、ブラウザーのウインドウが開きっぱなしならどうでしょうか? それまでのユーザーになりすまして、別のユーザーも利用できてしまいます。これは、使用中だけでなく、使用後と思っても正しくログアウトできていない場合、やはり別のユーザーが偶然に認証済みのページを見つけてしまうかもしれません。この問題は、すべてのアプリケーションに存在し、防ぐ方法はありません。言い換えれば、利用者が端末を適切な方法で利用しない場合には、脅威として必ず存在します。もちろん、INTER-Mediatorだけの問題ではないのです。アプリケーションを実装するときに、何もしないと認証が時間切れになる仕組みを利用したり、あるいはその時間を短くするなどで、他人がなりすます可能性を少しは減るかもしれませんが、力ずくで端末を開いた状態で取り上げられるようなことがあれば、やはり脅威となり得るのです。
ブラウザー上で稼働するJavaScriptのプログラムの場合、常に、変数の結果を参照したり、あるいは書き換えたり、場合によってはプログラムの書き換えができてしまう点に注意を払う必要があります。INTER-Mediatorでは、パスワードそのものは変数にも残さないようにしていますが、認証の手がかりとなる情報は変数に残しています。ある操作をすれば、それは参照できますが、原則として、本人が操作して見えてしまっても、本人である限りは問題ないと言えます。また、書き換えた結果、何もできなくなるのなら、それは書き換えた人の責任であり、開発する側は責任を取り必要はないと考えます。しかしながら、問題になるのは、そうして書き換えをした結果、別のユーザーになりすますことができるような状況があるかどうかです。INTER-Mediatorでは、その点はテストしており、認証時に別のユーザーになりすますことはできない仕組みになっています。少なくとも、開発時にはその点も考慮しました。
いずれにしても、JavaScriptのプログラムをクライアントで触れる点は問題になりそうですが、それによって他のユーザーになりすましたり、あるいはシステムやデータに損傷を加えることがなければ問題はないと言えます。ただし、この点について「バグが発見されていない」というだけのことで、この点について保証できるものではありません。潜在的には何かしらの問題点がある可能性があります。バグによる脆弱性の存在の可能性はINTER-Mediatorに限らずどんなソフトウェアにも存在します。何らかの懸念点があれば、アプリケーション開発者でもフレームワークの動作を検証する必要はあるかもしれません。
クロスサイトスクリプティング攻撃(XSS)とinnerHTML
クロスサイトスクリプティング攻撃(XSS)は、現在のWebアプリケーション開発において、必ず考慮されなければならないセキュリティ要件です。このXSSは、攻撃者が仕込んだ任意のJavaScriptが実行できる状況において発生し、その結果、例えばある特定のサイト向けのクッキー情報やページ上に表示された情報を、攻撃者のサイトにネットワーク転送してしまうような悪用が考えられます。つまり、ページ上で、任意のスクリプトが実行される状況を作ってしまうと、XSSが可能なセキュリティホールが発生します。これは、フレームワークに内在する問題ではなく、どんなフレームワークを使っても、アプリケーション側で意図せずに実装されてしまう可能性のあるセキュリティホールです。
XSSが発生する代表的な例が、「誰でも書き込みができるBBS」の事例です。せっかくのBBSを盛り上げるために、HTMLで自由にスタイルなどを設定するようにしていたとします。すると、書き込むメッセージにSCRIPTタグがあるとします。そのSCRIPTタグのプログラムは、書き込んだ人だけでなく、BBSを閲覧している人のブラウザー上でも実行します。クッキーの情報は、記録したドメインと同じドメインのサイトに接続しないと読み出せないといった制限がかかってはいるものの、スクリプトはまさにそのサイトにおいて実行しているので、他の人のクッキーを得ることができます。クッキーに含まれる情報で認証の成立を確認する状況は比較的利用されており、クッキーを第三者に取り出されてしまうと、その第三者によってなりすましのログインができてしまう場合も最悪はあり得ることになります。
この時、メッセージのHTMLテキストを表示するのに利用されるひとつの方法が、innerHTMLプロパティへの代入です。HTML文字列をそのまま代入できて便利なのですが、その文字列にSCRIPTタグが含まれている可能性があるため、innerHTMLの利用を控える必要があります。INTER-Mediatorでは、意図的にinnerHTMLを使用するように設定した場合と、認証パネルのカスタマイズの部分を除いて、innerHTMLは利用しないようになっています。通常のデータベースから得られたデータをタグ要素にマージするときには、DOMのAPIを使ってテキストノードとして追加をしています。
INTER-Mediatorで制限付きながらもinnerHTMLをサポートしている理由を説明しましょう。innerHTMLの危険性がある一方で、innerHTMLがあれば必ずXSS攻撃を受けてしまうということにはなりません。任意のHTMLテキストをデータとして書き込めるユーザーを限定すれば、第三者からのXSS攻撃は防ぐことができます。例えば、ある組織の通販サイトを運用するのであれば、メッセージを書き込むのは通常は通販会社の担当者です。利用者からのメッセージは例えばテキストだけにするということが一般的です。そういう状況においては、INTER-Mediatorは「任意のスクリプトを第三者が実行できる」状態にはなっていません。そのためにも、HTMLテキストを書込み権限は認証したユーザーだけに与えることをベースにして設計する必要があります。悪意のある人に書き込み権限を与えない方策を備えていれば、innerHTMLを全面的に排除していないことが、問題にはならないと考えています。すなわち、システムを運用する組織としての信頼関係があるユーザーだけに書き込みを許可すれば、XSSの発生となる根本原因を除去できるということです。もちろん、信頼した相手が攻撃をしないという前提の元で成り立つことです。
クロスサイトリクエストフォージェリ(CSRF)を排除する
クロスサイトリクエストフォージェリ(CSRF)は、最終的にはユーザーが意図しないサイトへ誘導されて、意図しない処理をさせられてしまう可能性のある攻撃です。攻撃者は攻撃用のWebページを公開し、あるユーザーがそのWebページを参照したとします。そのページの中にあるJavaScriptが、全く別のサイトに接続してリクエストを送るということができます。例えば、偽の「お買い得情報!」メールを送り、それを見たユーザーが攻撃者のサイトとは知らずにアクセスしたとします。その際に、そのサイトのファイル一式の中に、オンラインショップに接続して何か購入することを決定するようなスクリプトが組まれていたとしたらどうでしょう。そのユーザーがいつも使うオンラインショップであれば、認証がすでに有効になったままかもしれません。そのままJavaScriptのAJAXの機能を利用して自動的にオンラインサイトを操作して、購入決定までさせてしまうことは技術的には可能です。購入操作が無事完了したかに見えますが、商品は届かないかもしれません。もちろんオンラインショップのコントロールは難しそうですが、匿名の掲示板だとそれほど困難ではありません。ユーザー自身が知らないうちにあちこちのBBSに、攻撃者が記述したなりすましメッセージを書き込まれてしまうということにもなってしまいます。
INTER-Mediatorでは、作成したアプリケーションがCSRFを受け入れる脆弱性を持ってしまうことを防ぐための対策も組み入れています。対策方法としては、リクエストヘッダーにX-FromおよびOriginを利用する手法を利用しました。ポイントは、HTTPリクエストがサーバーに来た時にOriginヘッダーをチェックすることです。JavaScriptで作成した通信処理はXMLHttpRequestクラスを利用しますが、通信においてはそのページが生成されたサーバーのURLがクライアント側で自動的にOriginヘッダーの値に設定されます。そこで、INTER-Mediatorのサーバーでは自分自身のFQDN値を記録し、クライアント側ではX-Fromヘッダーにその値に付与するように動作します。元サーバーから自動的に付与されるOriginがX-Fromと同じであり、それがサーバーに設定したFQDNと同じであれば、CSRF攻撃ではないと判断できます。さらに、DNSサーバーの応答を操作することでの攻撃を回避するためにHostヘッダーも確認しています。攻撃側では攻撃対象のサーバーのFQDNはもちろん分かりますが、それを攻撃プログラムに組み込んでもDNSが正しく稼働している限りはOriginは攻撃者のサーバーになるので、OriginとX-Fromは一致せずCSRF攻撃であるとみなして処理はスキップします。X-Fromを攻撃者のサーバーと同じに設定しても、サーバーに設定したFQDNとも違うので、やはり攻撃が成り立ちません。CSRF対策として自分自身のFQDNをサーバーへ設定する方法については『8-1 定義ファイルの設定内容と外部での設定』で説明します。
データベースアカウントへのアクセス権設定
データベースエンジンには通常、アクセスするためのアカウントを設定可能です。INTER-Mediatorはデータをデータベースに保持するので、データベースアカウントを利用することで、可能な限り安全な運用が可能です。なお、SQLデータベースのひとつであるSQLiteではアカウントの設定機能はありません。設定の詳細については、『2-1 データベースからの取り出し設定』で説明があります。
SQLiteはネットワーク接続ができませんが、その他のINTER-Mediatorが対応するデータベースはすべてネットワークからの接続ができます。しかしながら、INTER-MediatorあるいはWebサーバーで稼働するWebアプリケーションから利用するときには、ネットワーク接続が必須なわけではありません。例えば、Webサーバーとデータベースサーバーが同一の場合は、データベースへのネットワーク接続は不要です。その場合は、localhostからの接続だけに限定することで、セキュリティ面では「他のコンピューターからの直接接続」は排除できて、不正なアクセスを許す要因は取り除かれます。また、Webサーバーとデータベースサーバーが異なるホストでも、データベース側ではWebサーバーからのアクセスのみを許可することで、他のコンピューターからの接続は排除できます。不要なネットワーク処理を利用できる状態にしないというのは、データベースに限らず、すべてのサーバー運用では鉄則と言える対処です。
MySQLやPostgreSQLなどSQLサーバーでデータベースを構築するときには、データベースサーバー自体に対して何でもできるスーパーユーザーを設定します。MySQLではroot、PostgreSQLではpgsql、SQL ServerではSAというユーザー名が一般的でしょう。また、FileMaker Serverは、各データベースにすべての設定変更が可能な管理者ユーザーを設定します。名称は任意ですが、Adminなどの名前をつけます。これらのアカウントには原則としてパスワードを設定します。PostgreSQLの場合は、OSのアカウントを利用するのですが、パスワードを利用してログインをします。これらデータベースエンジンのアカウントは、最初にデータベースを作ったり、スキーマの適用をする場合には必要になりますが、実運用、特に、Web経由のアクセスに応答するために利用することは、可能なかぎり避けるべきでしょう。理由は、「制限したユーザーでの利用の方が少しでも安全になる確率が高い」からです。
もし、Webアプリケーションからのアクセスが、データベースからの読み出しのみの場合、データベースのユーザーは、スキーマ定義ができないことはもちろん、必要なテーブルに対してSELECTのみ、あるいは読み出しのみの権限のみを与えておきます。また、Web側から更新があるのなら、スキーマ定義はできないものの、SELECT/DELETE/INSERT/UPDATE、あるいは読み書き権限を与えておきます。こうした、スーパーユーザーでないユーザーの運用制限を加えておくことで最悪の事態が起きた場合でもデータの安全が確保されることになります。最悪の場合に考えられるのは、ソースコードごと漏洩してデータベースのスーパーユーザーが知られてしまう可能性があることや、設定を誤り、抜け道を作ってしまい、読み出ししかできないはずが認証もなくデータを消すことができてしまったといったことがあります。
もし、サーバー管理を正しく構築し、ソリューションにセキュリティホール無く作ることができれば、スーパーユーザーでも問題ないのかもしれません。しかしながら、一種の保険として、データベースへのアクセス権を必要以上に広げないようにすることをお勧めします。そんなことは不要、あるいは過剰と思われるかもしれませんが、サーバー運用において不要なポートを閉じるのと同様な、予防的な措置として検討しましょう。
なお、プロバイダのレンタルサーバーでは、スキーマ定義や読み書きができるひとつのアカウントしか使えないことがあります。その場合は仕方ありませんので、他の手法でセキュリティを確保することにします。
セクションのまとめ
Webアプリケーションは、「サーバー側を誰もが自由に参照したり改変することはできない」という前提のもとで、セキュリティ対策を行っています。つまり、Webサーバーにログインできるユーザーを制限することが大前提です。また、データベースを利用するためのユーザーについても、余分な処理をできないようにしておくことで、他の問題が発生してもセキュリティが確保される確率は幾分は高くなるでしょう。
7-2ユーザー認証とアクセス権適用を行う仕組み
INTER-Mediatorは従来形式のWebアプリケーションと違い、クライアントサイドでの処理がむしろ主になっています。認証とアクセス権適用の考え方については、クライアントとサーバーでの役割が従来とは異なります。ただし、アプリケーション利用者にとっては違いがないようになっています。このセクションでは、INTER-Mediatorの認証やアクセス権の機能に加えて、その実現に必要なことを説明します。
認証とアクセス権
改めて、「認証(Authentication)」についての定義を記載します。認証は、利用者の特定を行い、アカウントが確定することです。ユーザー名とパスワードを入力するのが認証の代表的な手法です。何らかの方法で、アカウント、つまりシステムに記録された利用者のどれかを確定することが認証で実現されることです。もちろん、他のアカウントになりすましたり、他のアカウントとして振るまえてしまうということがないようにする必要があります。ユーザー名とパスワードを使う方式ではさまざまなセキュリティ的なリスクがありますが、一番大きいものが「パスワードの漏洩」です。言い換えれば、パスワードはそのアカウントに対応する人しか知りえないという前提の下で成り立つセキュリティの確保の手法です。
「アクセス権(Authorization)」は、「認可」とも呼ばれます。通常は認証によって確定したアカウントに対する処理範囲の制限を行う機能です。制限は、アプリケーション開発者やあるいはシステム管理者によって指定されます。一般には、特定の処理だけに制限するような設定、ないしは特定のアカウントに対してだけ特定の処理の許可を与えるといった設定が可能です。アクセス権についてのセキュリティ的なリスクとしては、もちろん、フレームワークのバグや、バックドアなどのアクセス権回避手段が存在しないといった、フレームワークの実装面のことがあります。また、アクセス権の設定は複雑になりがちであり、設定のミスが起こりやすいとも言えるので、作成したアプリケーションの十分な実証テストは欠かせません。
アカウントとグループ
INTER-Mediatorは、アカウントとして「ユーザー」「グループ」、そして内部的なアカウントとして「クライアント」の3種類をサポートします。グループは、ユーザーの集合ではありますが、グループにグループを所属させることができます。ユーザーは自分が所属するグループの一覧を得て、アクセス権の適用を受けます。
ユーザーのレコードは、データベースエンジンに組み込まれたユーザーを利用する方法(ネイティブ認証)、アプリケーションが利用するデータベースに含まれるテーブルあるいはビューを利用する方法(ユーザー認証)が用意されています。LDAPやOAuth2による認証にも対応していますが、その場合には後者のアプリケーションが利用するデータベースでのユーザー用テーブルは必須となります。
一方、グループについては、INTER-Mediator側で定義したものだけが指定できます。データベースエンジン側で定義したものに対してのアクセス権設定はできません。ユーザー認証時はもちろん、ネイティブ認証時にもアプリケーションが利用するデータベースにグループを管理するためのテーブルを定義し、そのグループレコードの利用のみです。
クライアントのアカウントは、通信のやり取りが発生したときにINTER-Mediatorによって自動的に割り振られるコードです。専用のテーブルは不要ですが、認証のための情報を記録するテーブルのひとつのフィールドとして記録されます。このクライアントコードを利用して、クライアント、正確には「ブラウザーのウインドウ」を識別します。
ユーザーのテーブル
ネイティブ認証以外の手法では、ユーザーのテーブルが必要です。既定のテーブル名は「authuser」としています。この名前は設定で別のものにもできますが、以下、特定しやすいように、ユーザーアカウントのテーブルはauthuserテーブルと呼ぶことにします。なお、テーブル名は任意の名前を指定できますが、フィールド名は決められたもののを使用する必要があります。表7-2-1に必要なフィールドを記述します。認証だけならテーブル自体は読み込みのみのアクセス権でも構いませんが、パスワードの変更をログインパネル上で行う場合には、パスワードのフィールドは書き込み可能である必要があります。また、テーブルの内容をアプリケーションで追加編集する場合には、一般には読み出しだけでなく変更や追加、削除の権限も必要になります。サポートするデータベースのサンプルファイルあるいはサンプルスキーマには、authuserテーブルそのものあるいは生成コマンドが含まれているので、自分で作成するアプリケーションの場合はその部分をコピーすると良いでしょう。
フィールド名 | 型の例 | 説明 |
---|---|---|
id | INT AUTO_INCREMENT | 連番の数値を入れて、キーフィールドとする |
username | VARCHAR(48) | ユーザー名(重複した名前が定義されないようにする) |
hashedpasswd | VARCHAR(48) | パスワードのハッシュ値(パスワード変更があるなら要書き込み) |
VARCHAR(100) | ユーザーのメールアドレス(メールアドレスをユーザー名にするときには必須) | |
limitdt | DATETIME | LDAP、OAuth2で必要。キャッシュしたアカウントの期限 |
パスワードのフィールドには、パスワードはそのまま入れずにダイジェスト関数によって処理された値を使います。計算方法は以下のとおりです。独自に生成する場合には、saltを常に異なる値にすることが必要です。なお、JavaScript側のライブラリの制約により、パスワードおよびsaltはASCII文字として表現可能な範囲にします。コントロールコードや漢字は利用しないようにします。シェルスクリプトで生成する場合のサンプルは、INTER-Mediatorのレポジトリにあるdist-docs/usergenerator.shを参照してください。
pw:パスワード
salt:4バイトのソルト値
sha1():SHA-1によるダイジェスト値を求める関数
hex():16進法による表記に変換する関数
+:文字列の結合
hashedpasswdフィールドの値 = hex(sha1(pw + salt)) + hex(salt)
これらのフィールドは必須のフィールドですが、任意のフィールドを追加してもかまいません。例えば、VARCHAR(20)型のrealnameフィールドに、ユーザーのフルネームを入れておけば、ログインした人の氏名をページ上に表示するようなことにも利用できるでしょう。
もし、すでに別の名前でユーザーテーブルが作られているのであれば、SQLデータベースの場合には、authuserビューを定義し、フィールド名はビュー定義のコマンドで別の名前に付け替えるような記述を行えば良いでしょう。FileMakerの場合には、レイアウト名をauthuserにすることで、任意のテーブルを利用できます。ただし、既存のフィールドが作られている場合ではフィールド名が表7-2-1のようになっていない可能性があります。その場合はフィールド名を変更するか、あるいは計算フィールド等フィールドの追加を行い、表に示したフィールドが存在するように見えるようにします。
グループのテーブル
グループを構成するテーブルは2つ必要です。まず、ひとつはグループそのものを定義する「authgroup」テーブルです。加えて、ユーザーやグループの所属関係を定義する「authcor」テーブルです。いずれも、任意の名前を利用できますが、テーブルの種類を特定する場合、authgroupテーブル、authcorテーブルと呼ぶことにします。表7-2-2と表7-2-3に、必要なフィールドを記述します。
フィールド名 | 型の例 | 説明 |
---|---|---|
id | INT AUTO_INCREMENT | グループを識別するための番号 |
groupname | VARCHAR(48) | グループ名 |
フィールド名 | 型の例 | 説明 |
---|---|---|
id | INT AUTO_INCREMENT | レコードを識別するための番号 |
user_id | INT | 所属するユーザーのidフィールドの値 |
group_id | INT | 所属するグループのidフィールドの値 |
dest_group_id | INT | 所属されるグループのidフィールドの値 |
authgroupテーブルは単にグループ名とid番号の割り当てのためのものです。authcorテーブルのひとつのレコードでは、user_idフィールドもしくはgroup_idフィールドのどちらかを入力し、一方はNULL(FileMakerでは"")にします。dest_group_idフィールドにはauthgroupテーブルに存在するidフィールド値を指定します。このauthcorテーブルのフィールドは、authuserおよびauthgroupテーブルのidフィールドの値を設定します。ユーザー名やグループ名を指定するのではありません。
ユーザーレコード生成のためのスクリプト
データベースのスキーマを読み込む段階で、すでにいくつかのユーザーを作っておきたい場合もあるでしょう。もちろん、自分でハッシュを計算することで可能ですが、INTER-Mediatorには、macOSあるいはLinuxで利用できるユーザーレコード作成のコマンドが用意されています。INTER-Mediatorフォルダー内のdist-docsフォルダー内にある「passwdgen.sh」というシェルスクリプトです。
このシェルスクリプトを使えば、hashedpasswdフィールドの値を求め、場合によっては、SQLステートメントの形式で得られます。INTER-Mediator/dist-docsがカレントディレクトリにあるとして、例えばリスト7-2-2のようにコマンド入力できます。行の最初に$があるのがコマンド入力行です。最初のコマンド例は、引数として--userでユーザー名、--passwrodでパスワード文字列を指定しており、出力は、CSVファイルの形式で、ユーザー名、パスワード、hashedpasswdフィールドの値の順に表示されます。ソルトは乱数で生成しています。2つ目のコマンド例は、さらに、--sqlを引数に指定しており、これにより、SQLステートメントの形式で出力をしています。
$ ./passwdgen.sh --user=test --password=testpassword
'test','testpassword','d72f7de01c7b2c16bf56dc9d8d501204f454b75e566d2c44'
$ ./passwdgen.sh --user=test --password=testpassword --sql
INSERT authuser(username,initialpass,hashedpasswd) VALUES('test','testpassword','5221ba90506becd7dcef0550ad344bec1173ca832b496020');
一方、CSVファイルを用意して、そのファイルを「./passwdgen.sh --sql --csv=CSVファイルのパス」のように指定すると、上記の処理をCSVファイルから取り出して行うので、多数のユーザー用のSQLステートメントを生成できます。CSVファイルは1列目がユーザー名、2列目がパスワードです。カンマで区切りますが、それぞれのフィールドは、シングルクォートやダブルクォートで囲っても構いませんし、囲わなくても構いません。
同じフォルダーに、usergenerator.shというスクリプトファイルもあり、こちらは、パスワードの自動生成などを行います。なお、引数は特に取りません。なお、これらのスクリプトでは不満がある場合もあるかもしれません。いずれもシェルスクリプトですので、必要に応じて改良して使ってください。また、スクリプトのソースはユーザー自動生成の方法の参考になると思います。
ハッシュ値用テーブルの内容
さらに認証を行う場合、ネイティブ認証でもユーザー認証でも、表7-2-4に示すテーブルが必要です。このテーブル名についてもカスタマイズは可能ですが、ここでは既定値の「issuedhash」テーブルと呼ぶことにします。通信を行うたびにレコードを生成するので、1回のページ表示でたくさんのレコードを作成します。そのため、このテーブルをFileMakerで運用するにはパフォーマンス上の問題が発生しますが、FileMaker Serverで運用しつつ、issuedhashテーブルのみをSQLiteで運用するということもできるようになっています。FileMakerではこの方法により、パフォーマンスを大きく損なうことなく認証処理ができるようになっています。
フィールド名 | 型の例 | 説明 |
---|---|---|
id | INT AUTO_INCREMENT | レコードを識別するための番号 |
user_id | INT | authuserテーブルのキーフィールドとなるid値 |
clienthost | VARCHAR(48) | クライアントを識別する自動生成されるコード |
hash | VARCHAR(48) | チャレンジに使うハッシュ値。実際には24バイトの16進数文字列 |
expired | DateTime | チャレンジの有効期限を示すタイムスタンプ値 |
このテーブルの意味を理解するには、INTER-Mediatorのクライアントサーバー間でのプロトコルを理解する必要があります。ここで解説は行いますが、機能を利用する上ではこの仕組みまで知る必要はありません。セキュリティのアセスメントが必要な方は頑張って理解してください。
表7-2-5は、クライアントからサーバーに対してデータベースアクセス等の要求がある場合のやり取りを示したものです。変数名と計算方法はリスト7-2-3に示します。このプロトコルは、ログインパネルでユーザー名とパスワードを入力した後に遂行されるものとしてください。まず、単にアクセス要求と応答があるのではなく、チャレンジ要求が最初にあり、チャレンジをもとに要求を出したときに、サーバー側で認証が判定されるという点が大きな流れです。もちろん、認証が成立したら、データベースから取り出したデータを返したり、更新処理に入ります。認証が成立しなければ、ログインパネルを再度表示し、このプロトコルを最初からやり直します。
クライアント側での処理 | 転送される認証情報 | サーバー側での処理 |
---|---|---|
チャレンジ要求 | →user→ | データベースよりhpwを取得しsaltを求める |
chを乱数より生成,cidが未発行なら乱数で生成 | ||
user, cid, ch, 日付時刻をデータベースに記録 | ||
resを求める | ←salt, cid, ch← | |
データベース要求 | →cid, user, res→ | issuedhashテーブルより,cidからチャレンジを取得 |
res'を計算し、resと同じなら認証成立 |
user:ユーザー名(ユーザーが入力)
pw:本来のパスワード(ユーザーが入力)
salt:ユーザーごとに異なるソルト(4バイトのASCII文字)
hpw:データベースに保存されているパスワードのハッシュ値
pw':入力したパスワード
cid:クライアントid(16進数で記述)
ch:サーバーから送られるチャレンジ(16進数で記述)
res:クライアントから送られるレスポンス
res':サーバー側の情報から得られるレスポンスの期待値
sha1(m):SHA-1によるダイジェスト値を求める関数
hmac(k, m):HMAC-SHA256で求められるMAC値を求める関数。ハッシュ関数はSHA-256,鍵がk,メッセージがm
hex(m):16進法による表記に変換する関数(hex(m) + hex(n) = hex(m + n)が成り立つ)
+:文字列の結合
hpw = sha1(pw + salt) + salt
res = hmac(sha1(pw' + salt) + salt, ch)
res' = HMAC(hpw, ch) = hmac(sha1(pw + salt) + salt, ch)
pw = pw' であれば res' = res となる
まず、ログインパネルでユーザー名とパスワードを入力します。そして、ユーザー名(user)をサーバーに送ります。サーバー側では、そのユーザーのパスワードハッシュ値(hpw)とidフィールドの値をauthuserテーブルから取得します。hpwの最後の8バイトからソルト(salt)が得られます。そして、クライアントid(cid)とチャレンジ(ch)を乱数で生成します。そして、issuedhashテーブルにレコードを作成して、ユーザーのid値、cid、ch、日時を記録します。クライアントへは、応答としてsalt、cid、chを送ります。クライアントはデータベースの要求に加えてres値を計算して、サーバーへの要求に付加します。res値は、応答で得られた値と、入力したパスワードから求めておきます。また、要求にはuser、cidも返します。要求を受け取ったサーバーは、issuedhashテーブルを検索して、chを求めます。そして、ユーザー名から得られるsalt、返されたcidをもとに、res'を計算します。このres'がresと等しければ、クライアント側で正しいパスワードが入力されたと判断できるということです。resとres'は、式の上ではpwとpw'の部分だけが違います。
この手法の大きな特徴は、ネットワーク上に流れるデータからパスワードを求めることができないということです。ネットワーク上のデータは、user、salt、cid、ch、resであり、これらの値からパスワード自体を求めることはできません。ハッシュ値hpwをデータベースに保存はしますが、パスワードはもちろん、そのハッシュ値自体もネットワーク上を流れないということです。この一連の作業で使われるチャレンジ(ch)は、サーバー側では共有しません。原則として1回のやり取りの間だけ有効になり、リクエストがあるたびに生成をします。したがって、resの値は要求ごとに異なる値になります。
クライアントid(cid)については、可能な限り再利用を行います。この認証だけなら、cidでなくても認証はできそうに見えるかもしれませんが、同一ユーザーで同時に複数のページを利用している場合があることを考えれば、チャレンジ要求とデータベース処理要求を結びつける意味では必要です。
なお、認証をすれば通常は1回の通信が2回へと倍になるのではないかと思われるかもしれませんが、より効率的な動作をします。データベース処理に対する応答には、次の要求のためのチャレンジ(ch)などのデータがすでに含まれていて、2回目以降は、データベースの応答と次のチャンレンジ応答を1回の応答で済ませてしまいます。したがって、認証がない時にn回の通信をする場合、認証があればn+1回の通信を行うことになり、1回、つまり最初のチャレンジ要求だけが増えることになります。
その他の手法
INTER-Mediatorでのさまざまな手法を紹介する前に、INTER-Mediator外で適用できる手法を紹介しましょう。まず、最初に、WebサーバーでサポートするHTTP認証です。Apacheだとモジュールの追加で認証ができるようになります。.htpasswdファイルに設定を記述するなどの手法があります。INTER-Mediatorは、HTTP認証が機能するサーバー内で稼働することもできます。単に認証だけでいいのであれば、INTER-Mediatorの認証機能は一切使わないで、Apache側の設定だけで認証はできます。
セキュリティを高める手法として、クライアントのIPアドレス制限をすることがあります。Apacheの設定ファイルで、アクセス可能なアドレス範囲を指定することができるので、特定の場所からのアクセスだけを許可したいような場合には、IPアドレスによる制限を有効に使うことできるでしょう。あるいは同等な仕組みをファイアウォールで実現してもいいかもしれません。
演習ユーザー管理の簡易アプリケーションを使ってみる
INTER-Mediatorにはauthuser, authgroup, authcorテーブルの内容を編集する簡易アプリケーションが付属しています。テーブルの定義以外に、このアプリケーションをベースにして、独自のユーザー管理アプリケーションを作成することもできるでしょう。この演習では、単にアプリケーションの存在と動作の確認だけ行います。実際の利用は、この後のセクションで何度か出てきます。
ユーザー管理アプリケーションを参照する
設計内容を確認する
- authuser(view/table=authuser)
- belonggroup(view/table=authcor)relation有り
- groupname(view/table=authgroup)
- authgroup(view/table=authgroup)
- groupingroup(view/table=authcor)relation有り
ユーザーとグループの対応関係を確認する
user_id | group_id | dest_group_id |
---|---|---|
1(user1) | NULL | 1(group1) |
2(user2) | NULL | 1(group1) |
3(user3) | NULL | 1(group1) |
4(user4) | NULL | 2(group2) |
5(user5) | NULL | 2(group2) |
4(user4) | NULL | 3(group3) |
5(user5) | NULL | 3(group3) |
NULL | 1(group1) | 3(group3) |
演習のまとめ
- INTER-Mediatorでの認証やアクセス権設定に使うテーブル、authuser、authgroup、authcorの関係を確認しました。
- これらのテーブルのデータ処理を行えるWebアプリケーションがあるので、実際のアプリケーション開発ではこれを元にユーザー管理機能を構築すると良いでしょう。
セクションのまとめ
INTER-Mediatorでの認証やアクセス権設定では、ユーザーやグループを使用します。それらは、authuser、authgroup、authcorのそれぞれのテーブルに記録しておくのが基本です。これらのテーブルの内容を編集するためのWebアプリケーションがINTER-MediatorのレポジトリのAuth_Supportフォルダーに作られています。さらに、認証をチャレンジ-レスポンスによって行うためのissuedhashテーブルも必要です。認証のための通信では、パスワードやそのパスワードから求められてデータベースに記録されたハッシュ値を通信経路上に流すことなく、認証が行われます。
7-3認証とアクセス権の設定
認証やアクセス権に対する設定は、定義ファイルに行います。これらの設定を、JavaScriptでカスタマイズすることはできません。言い換えれば、設定はクライアント側から変更することを一切できないようにすることで、セキュリティの確保を行っています。このセクションでは、設定方法と設定例を説明します。
認証に関する設定を行う場所
認証やアクセス権に関する設定は、定義ファイルで行います。JavaScriptのAPIは用意していないので、原則として、すべて定義ファイル側で行うと考えてください。設定項目は多岐に渡ります。まず、リスト7-3-1に示したのは、定義ファイルのPHPによる記述で記述可能なキーをすべて示したものです。それぞれのキーに対する設定内容は、後で示します。IM_Entry関数の最初の引数(コンテキスト定義)に指定するコンテキスト定義に設定する項目があり、加えてIM_Entry関数の第2引数(オプション設定)に指定する項目もあります。
IM_Entry(
array( //コンテキスト定義
array(
"name"=>"context", // コンテキスト名
"authentication"=>array(
"all" => array(
"user" => array( .... ),
"group" => array( .... ),
"target" => "....",
"field" => "....",
),
"read" => array(
/* "all" と同様 */
),
"update" => array(
/* "all" と同様 */
),
"create" => array(
/* "all" と同様 */
),
"delete" => array(
/* "all" と同様 */
),
),
"protect-writing" => array( .... ),
"protect-reading" => array( .... ),
),
),
array( //オプション設定
"authentication"=>array(
"user" => array( .... ),
"group" => array( .... ),
"user-table" => "string",
"group-table" => "string",
"corresponding-table" => "string",
"challenge-table" => "string",
"authexpired" => "string",
"storing" => "cookie|cookie-domainwide|session-storage",
"realm" => "string",
"email-as-username" => "boolean",
"issuedhash-dsn" => "string",
),
"media-context" > "string",
),
....);
定義ファイルエディターでのコンテキスト内の設定は図7-3-1のようなもので、「Show All」ボタンをクリックすることで表示されます。最初の状態では認証関連の設定は見えていません。このうち、リスト7-3-2で、「array(...)」つまり配列で指定が必要な項目については、半角のカンマ(,)で区切ってテキストを記述することで、それぞれを要素とする配列をキーに対する値として設定します。設定値については、表7-3-1に示します。表に記載の内容も、順次説明します。
指定場所 | キー | キー | 値と動作 |
---|---|---|---|
コンテキスト/authentication | all | CRUDのすべての操作に対する設定をまとめて与える | |
user | ユーザー名の配列を指定すれば、それらのユーザーのみに許可が与えられる。省略するとすべてのユーザー | ||
group | グループ名の配列を指定すれば、それらのグループのみに許可が与えられる。省略するとすべてのグループ | ||
target | "table"あるいは省略の場合テーブルに対してアクセス権を設定、"field-user"あるいは"field-group"なら次のfieldキーの値で指定したフィールドにあるデータをユーザー名ないしはグループ名として、レコード単位でそのユーザーあるいはグループに対してのみアクセス権を付与する | ||
field | レコード単位のアクセス権設定において、ユーザー名やグループ名が入力されるフィールドの名前 | ||
read | テーブルからの読み出し(クエリー)に対するアクセス権を設定する | ||
user/group/target/field | ("all"の場合と同じ) | ||
update | レコードへの更新に対するアクセス権を設定する | ||
user/group/target/field | ("all"の場合と同じ) | ||
create | テーブルへの新規レコード作成に対するアクセス権を設定する | ||
user/group/target/field | ("all"の場合と同じ) | ||
delete | テーブルからのレコード削除に対するアクセス権を設定する | ||
user/group/target/field | ("all"の場合と同じ) | ||
media-handling | trueを指定すると、メディア向けのチャレンジを生成してクライアントに送る | ||
コンテキスト | protect-writing | フィールド名を配列で指定する。これらのフィールドはクライアントからの更新を受け付けなくなる | |
protect-reading | フィールド名を配列で指定する。これらのフィールドはクライアントからの読み出しを受け付けなくなる |
定義ファイルエディターでのオプション設定にある認証関連の設定は図7-3-2のようなものです。こちらにも、userおよびgroupは配列での指定が必要であり、複数の要素を設定する必要がある場合にはカンマで区切って指定します。設定値については、表7-3-2に示します。表に記載の内容も、順次説明します。
指定場所 | キー | 値と動作 |
---|---|---|
オプション/authentication | user | ユーザー名の配列を指定すれば、それらのユーザーのみに許可が与えられる。値が「database_native」ならば、ネイティブ認証。省略するとすべてのユーザー |
group | グループ名の配列を指定すれば、それらのグループのみに許可が与えられる。省略するとすべてのグループ | |
user-table | ユーザーテーブルの名前がauthuserでない場合にここにその名前を指定する | |
group-table | グループテーブルの名前がauthgroupでない場合にここにその名前を指定する | |
corresponding-table | グループ対応テーブルの名前がauthcorでない場合にここにその名前を指定する | |
challenge-table | ハッシュテーブルの名前がissuedhashでない場合にここにその名前を指定する | |
storing | 認証情報を記録して再利用する方法を指定する。"cookie"ならそのディレクトリ内で共有、"cookie-domainwide"は同一ドメイン内で共有、"session-storage"はブラウザーのデータベースを利用して共有 | |
authexpired | 認証情報の記録のタイムアウト。ここで指定した時間、通信がないと認証状態を無効にする | |
realm | 認証領域名。ログインパネルに表示される。また、認証状態の記録時のキー名に付加される。この文字列には半角のドットは指定しない | |
email-as-username | emailフィールドの値をユーザー名として受け入れる場合にはtrue、省略はfalseと同じ | |
issuedhash-dsn | isshedhashテーブルが存在するデータベースのDSNを指定する。省略時は他で指定されるデータベースでisshedhashテーブルを運用 | |
オプション | media-context | メディアアクセスに認証を設定するとき、ここで指定した名前のコンテキストにアクセスしたときにメディアの認証用のトークンをクライアントに届ける |
ユーザーやグループなど、同様な設定がコンテキスト定義にもオプション設定にもあります。もちろん、オプション設定は、定義ファイルで定義したすべてのコンテキストに対して適用されるのに対して、コンテキストへの定義はそのコンテキストだけに適用されるものです。ただし、すべての設定が両方にあるわけではなく、実運用を考慮してオプション設定には全体的な設定、コンテキスト定義には詳細な設定が存在するようにしました。
設定例による認証の設定
設定の詳細も重要ですが、複雑なので、ここと次のセクションで、用途に応じてどのような設定を行うのかを例で示します。以下の表記は定義ファイルでのPHPでの記述を行います。なお、いずれも、『7-2 ユーザー認証とアクセス権適用を行う仕組み』で説明したテーブルが用意されていて、ユーザーやグループが定義されていることを前提として説明します。
すべてのコンテキストで認証が必要
あるページにおいて、認証を必要とするものの、認証さえできれば、ページ内での読み書きすべてができるといった、非常にシンプルな動作の場合は、リスト7-3-2のように、オプション設定にauthenticationキーの値さえあれば構いません。なお、定義ファイルでは、値が「array()」という設定はできません。そのため、storingキーで認証継続の設定や、authexpiredキーで認証継続の時間等をデフォルトと同じでもいいので設定をして、authenticationキーに要素のある配列が設定されるようにします。
IM_Entry(
array( /* コンテキストにauthenticationの指定はなし*/ ),
array(
'authentication' => array( ), //項目のみ
),
...
)
特定のユーザーのみがログインできる
ページ全体にわたって、特定のユーザーのみがログインして利用できるようにするには、リスト7-3-3のように、オプション設定のauthenticationキーに、userキーの配列を指定します。この設定は定義ファイルエディターではuserの項目に「user3, user4」のように指定をします。カンマの前後の空白は無視します。したがって、読みやすくするために自由に空白を入れることができます。ここでは、user3ないしはuser4はログインができて、ログイン後は読み書きの処理が全て許可される状態になります。その他のユーザーは、たとえパスワードが正しくても、認証エラーとなってログインができず、データ処理は一切できない状態になります。なお、この目的だけなら、オプション設定のauthenticationキーは不要ですが、認証継続の方法などの指定が必要ならば、オプション設定の記述を行います。
IM_Entry(
array( /* コンテキストにauthenticationの指定はなし*/ ),
array(
'authentication' => array(
'user' => array( 'user3', 'user4' ),
),
),
...
)
特定のグループのユーザーのみがログインできる
ページ全体にわたって、特定のグループに所属するユーザーのみがログインして利用できるようにするには、リスト7-3-4のように、オプション設定のauthenticationキーに、groupキーの配列を指定します。この設定は定義ファイルエディターではgroupの項目に「group1, group2」のように指定をします。カンマの前後の空白は無視します。ここでは、group1ないしはgroup2に所属するユーザーはログインができ、ログイン後は読み書きの処理が全て許可される状態になります。その他のユーザーは、たとえパスワードが正しくても、認証エラーとなってログインができず、データ処理は一切できない状態になります。なお、この目的だけなら、オプション設定のauthenticationキーは不要ですが、認証継続の方法などの指定が必要ならば、オプション設定の記述を行います。
IM_Entry(
array( /* コンテキストにauthenticationの指定はなし*/ ),
array(
'authentication' => array(
'group' => array( 'group1', 'group2' ),
),
),
...
)
ログインの継続方法と設定
ログインパネルでログインをした結果、その後にログイン状態を続けるための情報が、JavaScriptの変数に記憶されます。しかしながら、その記憶結果はページを移動すると消えてしまいます。ひとつのサイトで複数のページファイルを使って構成する場合、ひとつのページで認証が成功すると、別のページでもログインパネルが表示されずに、認証を継続してもらいたいと考えるでしょう。INTER-Mediatorはそのために、認証情報をページ間で継続する方法を提供しています。設定は、オプション設定にあるstoringとauthexpiredキーを利用します。
認証を継続するために保存するのは、ユーザー名とクレデンシャル、つまり、パスワードにハッシュ処理をした値です。この値は、データベースのauthuserテーブルのhashedpasswdフィールドに設定される値と同じものです。この値からパスワードそのものはハッシュ関数による処理値なので分かりませんが、他者に知られることによって、定義ファイルへなりすましてアクセスができるようになるため、知られては困る情報です。
storingキーに設定できる文字列は、session-storage、cookie、cookie-domainwide、noneのいずれかです。noneだと保存をしないので、ページが切り替わると認証もやり直しが必要です。既定値、つまり無設定の場合はcookieが選択されます。cookieとcookie-domainwideはどちらもクッキーを利用して、authexpiredキーで指定した秒数の生存期間を持つクッキーを生成します。前者はページのディレクトリ、後者はページが含むドメイン全体にわたって認証情報が共有されます。authexpiredキーは省略すると、3600秒つまり1時間と成ります。この設定は、最後にアクセスしてからの時間であり、「何もしない時間がauthexpiredだけあればクッキー情報は消える」ということになります。
クッキーやセッションストレージに記録される情報を表7-3-3にまとめました。なお、realmキーの値を設定すると、これらのキーの後にアンダーラインに続いて指定されます。realmが「Sample」なら、最初のキーは「_im_username_Sample」となります。異なるサイトで別々に認証したいのは当然なので、通常はサイトに固有のrealmの値を指定する必要があるでしょう。
キー名 | 記録内容 |
---|---|
_im_username | ログインしているユーザー名 |
_im_credential | パスワードにハッシュ処理をした値 |
_im_mediatoken | 画像等のメディア認証のための情報 |
_im_crypted | 暗号化したパスワード(ネイティブ認証、LDAP認証で使用) |
クッキーでの記録はブラウザーを終了しても時間が来るまで残るので、その意味では便利です。期間を1週間くらいにしておくと、ほぼずっと認証が継続されるようになります。authexpiredキーの値を0にするとWebブラウザー終了時にクッキーが削除されます。しかしながら、クッキー自体がHTTPの通信でネットワーク上に送信されます。サーバー側ではその値は使用しないのですが、ネットワーク上に送信されるクッキー情報には注意を払う必要があります。したがって、クッキーを使用するときには、必ずTLSを使用してクライアントとサーバー間の通信を暗号化し、通信結果の傍受ができないようにしてください。
session-storageの場合はネットワークに流れないので、クッキーより若干安全と言えなくもないですが、クッキーやセッションストレージに共通の脅威があります。それは、ログイン中のブラウザーを他人が使うことです。あるユーザーがログインをしてサイトを利用している時、PC/Macを操作できる状態で席を立つとします。すると、別の人が開いているブラウザーを使用して、元からログインをした人になりすますことができます。この問題は、すべての認証を伴うWebサイトに発生する問題点であり、INTER-Mediatorも例外ではありません。もちろん、クリックするたびにパスワード入力をすれば防げるとはいえ、それではシステムを利用する気にはなれないでしょう。この問題は、「ログアウトしない」という点に集約できます。利用者が席をはずすときには、ログアウト、あるいはOSの機能を利用してロックするのが現在のPC/Macの利用の上では原則です。INTER-Mediatorはその対策はできないですが、それは他のフレームワークやシステムも同様です。
認証の動作の設定
通常は、authuserのusernameフィールドの値をユーザー名としますが、電子メールアドレスをユーザー名としたい場合もあります。その時は、オプション設定に、email-as-usernameキーと値を追加し、値はtrueにします。これで、authuserテーブルのemailフィールドの電子メールアドレスでも認証ができます。ただし、内部的には、usernameの値を使うので、usernameは一意な値を指定しておく必要があります。電子メールでの認証を可能にした場合、電子メールアドレスをusernameフィールドの値に変換するためにデータベース処理が増えます。SQLサーバーはその程度であればあまり問題にならないでしょうが、FileMakerの場合はパフォーマンスに影響が出る可能性があるので、慎重に導入しましょう。
オプション設定にあるissuedhash-dsnキーには、issuedhashテーブルに対するDSNを別途指定することができます。例えば、FileMakerデータベースではissuedhashテーブルのやり取りに多数のデータベース処理を行うと目に見えてパフォーマンスが落ちます。そこで、issuedhashテーブルをSQLiteで運用することができるように、こうした設定を追加しました。issuedhashテーブルは独立して利用できるように設計されています。authuserテーブルなどはFileMaker側に持ち、issuedhashテーブルだけをSQLiteで運用します。データベースを保存したパスなどを含むDSNを値として指定します。なお、INTER-Mediatorに含まれているdist-docs/sample_schema_sqlite.txtを利用してデータベースファイルを作り、それを利用することで問題ありません。他のテーブルは無視されます。こうして作成したSQLiteのデータベースを/var/db/imに置いたとすれば、issuedhash-dsnキーの値としては以下のように指定して利用することができます。
'issuedhash-dsn' => 'sqlite:/var/db/im/sample.sq3',
ネイティブ認証について
ネイティブ認証は、データベースに記録されているユーザーを利用した認証です。MySQLやPostgreSQLではSQLコマンドを使って定義し、パスワードの設定を行います。FileMakerの場合は、メニューからグラフィカルユーザーインターフェースで追加などができます。これらのユーザーを使った場合には、アクセス権の設定を、スキーマレベルで定義して、データベースエンジン側の仕事とするか、あるいはauthgroup、authcorテーブルを用意して、INTER-Mediator側での仕事にするか、2通りの方法があります。
ネイティブ認証の設定は、オプション設定のuserの設定に特別なキーワードを設定することで行えます。この設定があれば、authuserテーブルは使用しません。ログインパネルで入力したユーザー名とパスワードを利用して、データベースへのアクセスを行います。
IM_Entry(
array( ... ), // コンテキスト定義
array( // オプション設定
'authentication' => array(
'user' => array('database_native'),
),
),
array( 'db-class' => 'FileMaker_FX',),
...
);
ネイティブ認証では、クライアントからサーバーに対して、データベースアカウントのパスワードを伝達する必要があります。そこで、サーバー側に、非対称鍵を保持し、公開鍵をクライアントに送りそれを利用してパスワードを暗号化し、サーバーに伝達しています。この鍵は、OpenSSLがインストールされていればコマンドを入れれば生成できますが、サーバーを立てるごとに新たに生成して登録の必要があります。登録する場所はPHPのプログラムでの記述が必要なので、『8-2 PHPでの記述が必要な認証処理』で説明を行います。
ログインパネルとカスタマイズ
INTER-Mediatorではログインパネルを自動的に生成します。どのようなログインパネルなのかはこの後の演習の画面ショット等で確認してください。ログインパネルのページがあるわけではなく、既存のページで認証が要求されると自動的にオブジェクトをページに追加して、ログインパネルを表示します。背景が薄く見えるようなスタイル付けをしています。
なお、ログアウトは、ページネーションコントロールに表示されるので、ページネーションの表示の定義が行われていれば、ログアウトボタンもページ上に表示されます。あるいは、以下の1行のプログラムを実行するボタン等を追加してください。なお、この1行の実行にも、ページファイルを読み込むSCRIPTタグが必要です。
INTERMediatorOnPage.logout();
INTER-Mediator標準のログインパネルの動作等をカスタマイズするためのJavaScriptのAPIをリスト7-3-8に紹介します。いずれも、INTERMediatorOnPage.doBeforeConstructメソッド(『6-1 ブラウザーを判断するページ』を参照)内など、ページ合成の前に実行します。設定が全てプロパティとなっているので、代入文による利用がほとんどでしょう。
//認証の失敗回数の変更(既定値は4)
INTERMediatorOnPage.authCountLimit = 2;
//パスワード変更の機能をログインパネルに出さない
INTERMediatorOnPage.isShowChangePassword = false;
//ログインパネルのスタイル設定をしない
INTERMediatorOnPage.isSetDefaultStyle = false;
//ログインパネルのRealm名を置き換える
INTERMediatorOnPage.authPanelTitle = 'なんとかシステム';
//ログインパネル自体を独自のHTMLに置き換える
INTERMediatorOnPage.loginPanelHTML = "....";
ログインパネルの各要素は、スタイルシートを利用して、スタイルの設定ができます。ただし、「INTERMediatorOnPage.isSetDefaultStyle = false;」を実行して、INTER-Mediator側のスタイル設定をしないようにします。そして表7-3-4に示す、クラスあるいはID属性の要素に対して、スタイル設定を行います。もちろん、一部の設定のみを変更しても構いません。
分類 | キーワード | タグ | 設定対象 |
---|---|---|---|
class | _im_authpanel | DIV | 認証パネル全体 |
_im_authlabel | SPAN | 「ユーザー名」などのラベル | |
id | _im_username | INPUT | ユーザー名のテキストフィールド |
_im_password | INPUT | パスワードのテキストフィールド | |
_im_authbutton | BUTTON | ログインボタン | |
_im_newpassword | INPUT | 新しいパスワードのテキストフィールド | |
_im_changebutton | BUTTON | パスワード変更ボタン |
ログインパネル自体を完全に置き換えるには、リスト7-3-8のリストの最後にあるINTERMediatorOnPage.loginPanelHTMLプロパティへ、HTMLで記述した文字列の代入を行います。ここでのHTMLは、HTMLタグから始まるものではなく、通常はDIVタグで開始することになると思われます。この時、HTML内では、表7-3-4の1列目がidの要素が含まれているようにします。例えば、「<input type="text" id="_im_username"/>」などの要素が含まれている必要があります。少なくとも、最初の3つは必要ですが、パスワードの変更が不要なら、最後の2つの要素は用意する必要はありません。また、FORMタグは必要ありません。
パスワードのリセット
パスワードのリセットは、ログインパネルにその機能を含めてあります。この時、現在のパスワードを正しく入力しないと、パスワードの変更はできません。もし、独自にパスワード変更のページを作成したいとしたら、JavaScriptでリスト7-3-9のAPIを呼び出すことで可能です。
INTERMediator_DBAdapter.changePassowrd(ユーザー名, 旧パスワード, 新パスワード);
なお、現在の自分のパスワードが分からなくなってしまった場合は、事前に登録してある電子メールアドレスへのメッセージを利用してパスワードをリセットする機能もINTER-Mediatorで利用できますが、PHPを利用したプログラミングが必要です。こちらについては、『8-2 PHPでの記述が必要な認証処理』で説明を行います。
演習認証の実現とパスワード変更
この演習では、認証が必要なページを作成するための必要な記述と、実際の認証の動作を確認します。設定により、INTER-Mediatorによってログインパネルが表示されてログインができるようになります。また、ユーザーが自分自身のパスワードを変更する方法を説明します。
最初のページ用の定義ファイルに必要な設定を行う
db-classは「PDO」のままでかまいません。dsnに「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
db-classを「FileMaker_FX」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「192.168.56.1」、portに「80」、protocolに「http」、datatypeに「FMPro12」と入力します。
最初のページのページファイルの作成
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<script type="text/javascript" src="def24.php"></script>
</head>
<body>
<div id="IM_NAVIGATOR"></div>
<table>
<tbody>
<tr>
<th>作成日</th>
<td><input type="text" data-im="invoice@issued"/></td>
<td></td>
</tr>
<tr>
<th>タイトル</th>
<td><input type="text" data-im="invoice@title"/></td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>
ページを開くのに認証を必要とするように変更する
認証とパスワード変更の動作を確認する
認証の継続の様子を確認する
認証の継続の仕組みを確認する
Chrome以外のブラウザーでのクッキーの表示方法
[Firefoxの場合]
アドレスやツールアイコンがあるバーの右端にある「三」マークのアイコンをクリックし、パネルから「設定」を選択します。左側で「プライバシー」を選択し、「履歴」の中にある「Cookieを表示...」ボタンをクリックすると、サイトごとのクッキーを参照できます。
[Internet Explorerの場合]
F12キーを押して開発者ツールを表示し、「コンソール」タブの画面のコマンド入力プロンプトで、「document.cookie」と入力してreturnキーを押します。
[Safariの場合]
環境設定の「詳細」で、「メニューバーに“開発”メニューを表示」のチェックを入れます。クッキーを調べたいページが表示している状態で、「開発」メニューの「Webインスペクタを表示」を選択します。そして、「ストレージ」のタブを選択すると、左側にCookieや「セッションストレージ」の項目があるので、選択するとそれらを表示します。
演習のまとめ
- 定義ファイルのIM_Entry関数の呼び出しにおいて、第2引数(オプション)にauthenticationキーの設定を行うことで、すべてのコンテキストの利用に認証が必要になります。
- 認証の継続のために、セッションストレージやクッキーに、認証情報を保存することができます。クッキーの場合はブラウザーを再起動しても認証は継続しますが、データベースに記録するハッシュ関数から得られた値をネットワークに流すので、最低限でもTLSでの運用が必要です。
- パスワードの変更は、ユーザーによって、ログインパネル上で行うことができます。
このセクションのまとめ
認証のための設定は、多くは定義ファイル上で行うことで実現可能です。むしろ、データベースにユーザーやグループのテーブルを用意する方がよほど手間がかかる仕事と言えるでしょう。このセクションでは認証を実現する方法やそこでのさまざまな設定、ログインパネルやそのカスタマイズなどを説明しました。しかしながら、認証を伴うアプリケーションはセキュリティ上の問題を抱えやすいシステムでもありますので、どういう原理で認証が稼働していて、やってはいけないことや、運用上どうしても必要になることに対してなぜそのような結果になっているのかをなるべく正しく理解をするようにしましょう。
7-4コンテキストにおけるアクセス制御
アクセス権の制御は、基本となる4つのデータ操作のCRUD(Create Read Update Delete)について、それぞれ、どのユーザーあるいはグループに対して許可しているのかをコンテキスト定義で規定します。操作ごとのアクセス権は、オプション設定では指定はできず、個々のコンテキスト定義で行います。
設定例によるアクセス権の設定
前のセクションに続いて、用途に応じてどのような設定を行うのかを例で示します。以下の表記は定義ファイルでのPHPでの記述を行います。なお、いずれも、『7-2 ユーザー認証とアクセス権適用を行う仕組み』で説明したテーブルが用意されていて、ユーザーやグループが定義されていることを前提として説明します。また、設定の詳細は、『7-3 認証とアクセス権の設定』の最初の部分に表で示してあります。
読み出しはできて書き込みができないコンテキスト
読み出しはできても書き込みを一切できないようにしたい場合、認証の設定を使わない方法もあります。リスト7-4-1のように、コンテキストのviewキーの値には存在するビューやテーブル名を指定し、tableキーの値には存在しないエンティティ名(ビューあるいはテーブルの名前)を指定します。authenticationキーの設定はありません。もちろん、その状態で書き込みをしてもエラーになるということで何も起こらないことを意図したものです。ユーザーインターフェースで書き込み可能なものを配置しないだけでは、定義ファイルへネットワークを通じて直接アクセスされた場合に書き込みや更新ができてしまう可能性があります。この設定は、そうした意図しないアクセスからも、書き込み処理を排除してデータを保護します。単に読み出しだけでいいようなコンテキストは認証の有無にかかわらずよくあり、安全面からも、なるべくtableキーの値に存在しないエンティティ名を指定するようにすべきです。また、認証がされていても書き込みを排除したい場合には、この手法は有効です。
IM_Entry(
array(
array(
'name' => 'person',
'view' => 'person_view', //データベースには存在する
'table' => 'dummydummy', //データベースには存在しない
'records' => 1,
'key' => 'id',
),
),
array( ... ),
...
);
特定のユーザーが読み込みだけの権限でログインできる
特定のコンテキストで、認証を必要とし、加えて書き込みができないようにするには、リスト7-4-2のような定義ファイルを作成します。この例では、コンテキストのtableキーに存在しないテーブル名を指定して、書き込み処理がエラーになるようにしています。そして、adminユーザーは認証して読み出しができますが、書き込みができるユーザーは誰もいないという状態になります。この時、この定義ファイルの他に、authenticationキーのないコンテキストがあれば、どのユーザーもログインを行うことなくデータベースを利用できます。
IM_Entry(
array(
array(
'name' => 'mycontext',
'view' => 'mycontext',
'table' => 'dummydummy', //実在しないテーブル名
'authentication' => array(
'read' => array(
'user' => array( 'admin' ),
),
),
),
),
array( /* 'authentication' キーはなし */ ),
...
)
認証したユーザーに対して読み出しと更新のみを許可するコンテキスト
データベースの4つの主要な処理であるCRUDそれぞれに対するアクセス権を、コンテキスト定義の中で設定することができます。この時、read、update、create、deleteの4つの操作をすべて記述してください。そして、その中で、userあるいはgroupキーで許可を与えるユーザーやグループを配列で与えます。それぞれの操作で一切許可しないものは、架空のグループを割り当てておけば良いでしょう。リスト7-4-3のコンテキストmycontextでは、adminsグループのユーザーであればログインをして、読み出しと更新処理はできますが、dummyグループは存在しないグループなので、レコード作成や削除ができなくなります。ここでは、viewとtableキーがないので、「mycontext」テーブルに読み書き処理が行われます。
IM_Entry(
array(
array(
'name' => 'mycontext',
'authentication' => array(
'read' => array('group' => array( 'admins' ),),
'update' => array('group' => array( 'admins' ),),
'create' => array('group' => array( 'dummy' ),),
'delete' => array('group' => array( 'dummy' ),),
),
),
),
array( /* 'authentication' キーはなし */ ),
...
)
認証したユーザーに対して全ての操作を許可するコンテキスト
CRUDのそれぞれの操作に対する設定が全て同じであるのなら、allキーを使って1行で指定が可能です。リスト7-4-4のコンテキストmycontextでは、adminsグループのユーザーであればログインをして、データベースの全ての処理ができます。
IM_Entry(
array(
array(
'name' => 'mycontext',
'authentication' => array(
'all' => array('group' => array( 'admins' ),),
),
),
),
array( /* 'authentication' キーはなし */ ),
...
)
コンテキストでのフィールド単位の制限
コンテキスト定義の中では、特定のフィールドに対する書き込みや読み出しできないようにする設定が可能です。リスト7-4-5のように、protect-writingあるいはprotect-readingというフィールドの設定ができます。例えば、外部キーフィールドや、次のセクションで説明するユーザー名のフィールドに対して更新できないようにしたり、検索やソートでは使うものの読み出しはできないようにしたいフィールドがあれば、その名前を配列で指定します。IM_Entry(
array(
array(
'name' => 'person', // view, tableは省略
'records' => 1,
'key' => 'id',
'protect-writing' => array( 'id' ),
'protect-reading' => array( 'username' ),
),
),
array( ... ),
array( ... ),
false
);
演習コンテキストにアクセス権を設定する
コンテキストに対するアクセス権の設定を行ってみます。ここでの演習は、『7-3 認証とアクセス権の設定』の『認証の実現とパスワード変更』で作成したページを続けて利用します。データベースの4つの処理に対して、それぞれ可能なユーザーやグループの設定ができます。ここではグループの設定と、権限がない場合にはデータベース処理がなされないことを確認します。
アクセス権の設定と動作を確認する
レコード作成と削除の権限がない場合の動作
演習のまとめ
- コンテキスト定義にauthenticationキーでの指定を行うことで、データベースの4つの操作であるCRUDそれぞれに対して実施可能なユーザーやグループを定義できます。
- 誰にも操作されたくない場合には、存在しないグループを指定するのが確実かつ手軽な方法です。
このセクションのまとめ
コンテキスト定義において、CRUDの各操作が可能なユーザーやグループの指定ができるので、特定のユーザーに対するアクセス権の付与が可能です。また、tableキーの値に存在しないテーブル名を与えて、書き込み処理を阻止するという手段も利用できます。
7-5レコード単位のアクセス権とメディアデータのアクセス権
同一のテーブルでレコードごとに違うユーザーに読み書き権限を与えて運用できます。そして、各レコードは他のユーザーからは操作できないように保護されています。また、写真のファイル等の読み出しにおいて、レコード単位のアクセス権を適用することもできるので、画像などもユーザーごとに保護した状態で参照ができます。
レコード単位のアクセス権
同一のテーブルの個々のレコードに対してアクセス権を設定するためには、そのレコードが誰に対してアクセス権を持っているのかを何らかの方法で記録が必要です。そのために、そのテーブルに、ユーザー名あるいはグループ名を記憶するフィールドを設けて、そのフィールドに設定されているユーザーあるいはグループに対しての権限が与えられるような動作をフレームワークが行います。
設定はコンテキスト定義のauthenticationキーの配列の中で、操作名をキーにした配列で、targetキーとfieldキーを指定します。targetキーは、「field-user」ならfieldキーで指定したフィールドにある名前のユーザーに対して権限が与えられます。targetキーの値が「field-group」ならfieldキーで指定したフィールドにある名前のグループに対して権限を与えます。targetキーが「table」あるいは省略の場合には、テーブル全体で同一のアクセス権の設定となります。
fieldキーに指定するフィールドは文字型にします。そのフィールドには、authuserテーブルのusernameフィールドの文字列を入力します。主キーとなるidフィールドの値ではなく、ユーザー名の文字列を入力します。また、電子メールをユーザー名に使う場合でも、対応するusernameフィールドの値を設定します。
クエリーやレコード削除、更新の場合の検索条件に、ANDで「fieldのフィールド名=ログイン中のユーザー名」という検索条件を付与します。したがって、全ての操作で、他のユーザーのレコードに対してデータの削除・更新が行われることはありません。また、新規にレコードを作成するときに、fieldで指定したフィールドにユーザー名を自動的に設定します。こうして、レコードの生成から更新、削除に至る全ての場面で、ログインしているユーザーに権限のあるレコードだけが処理対象になるということです。また、言い換えれば、他人のレコードを削除しようとしても認証しない限りはできないのです。ただし、このAND演算がポイントであるため、FileMakerの場合に検索条件をORで構成している場合には、レコード単位のアクセス権の設定は適用できません。
リスト7-5-1はレコードごとのアクセス権を、chatというコンテキストに対して設定しています。オプション領域には、user1あるいはgroup2に所属するユーザーのみが、まず認証してログインできることが定義されています。その上でコンテキスト定義のauthenticationでは、全てのデータベースオペレーションに対して、userという名前のフィールドにユーザー名を記録して、各レコードはuserフィールドのユーザーだけが読み書きできるようになります。さらに、protect-writingキーで、userフィールドの更新を阻止します。この設定を行っても、新規レコードを作るときには、userフィールドにはログインしているユーザーのユーザー名が設定されます。
IM_Entry(
array(
array(
'records' => 100000000,
'name' => 'chat',
'key' => 'id',
'repeat-control' => 'delete',
'authentication' => array(
'all' => array(
'target' => 'field-user',
'field' => 'user',
),
),
'protect-writing' => array( 'user' ),
),
),
array(
'authentication' => array( // オプション設定
'user' => array('user1'), // ログイン可能なユーザー
'group' => array('group2'), // ログイン可能なグループ
),
),
array('db-class' => 'PDO'),
false
);
データベース外のファイルに対して認証アクセスする
データベースからデータを取り出すときに、認証を確認し、アクセス権を適用しますが、一方で、画像などのようにデータベースに入れない情報の取り出しについても認証やアクセス権を設定したい場合もあります。ここでは、画像ファイルなどのデータを「メディアデータ」と呼ぶことにします。
メディアデータを送信するための準備としては、メディアデータのファイルを保存するディレクトリをWebサーバーの公開ディレクトリ外に用意して、ディレクトリやファイルに適切な、すなわちWebサーバーのプロセスが、読み出し可能なアクセス権を設定しておくことです。そして、そのディレクトリのパスを、オプション設定(IM_Entry関数の2つ目の引数)のmedia-root-dirキーの値に指定します。これは、Webサーバーのドキュメントディレクトリ外にします。ドキュメント内部では、場合によっては偶発的にパスが漏洩してしまって、認証せずに画像を見られてしまうかもしれません。
画像などのメディアデータをWebブラウザーから参照させるには、定義ファイルへのアクセスを利用します。定義ファイル自体はINTER-Mediatorの本体を取り出したり、データベースアクセスを行って結果を送り返すなどさまざまな用途に利用しますが、URLのパラメーターにmedia=で値を指定すると、その値を相対パスとしたファイルの内容を取り出します。基準となるパスは、media-root-dirキーの値です。
例えば、nameキーの値がcontextというコンテキストがあって、そのコンテキストのimagefileフィールドに、画像ファイル名が入力されているとします。そのコンテキストにおいて、認証が通るか、オプション設定にあるユーザーやグループでの認証が通った時に、画像に対しても認証が通ったクライアントからのリクエストのみ受け付けるには、まず、コンテキストをリスト7-5-2のように記述します。必要最低限の記述を示します。
IM_Entry(
array(
array( //コンテキスト定義
'name' => 'context',
'key' => 'id',
'authentication' => array(
'media-handling' => true,
),
),
),
array( // オプション設定
'media-root-dir' => '/var/www/files',
'authentication' => array(
),
),
array('db-class' => 'PDO'),
false
);
media-handlingがあれば、このコンテキストに対して読み出しのリクエストがあった時、そのレスポンスに、ランダムなコードを返します。そのコードを、定義ファイルのアクセス時にクッキーとしてサーバーに送り、そのコードがサーバーによって発行されたものであるならば、認証されたものとみなします。サーバーとクライアントで認証情報をやり取りする方法はクッキーで固定されています。1回のレスポンスで返るコードは、複数のメディアデータへのアクセスに利用できるので、逆に言えば、セキュリティ面には注意が必要です。この方法でメディアデータに認証を設定するのであれば、TLS 1.2での通信回線上の暗号化が必要になります。
リスト7-5-2の定義ファイルがdef.phpだとして、contextコンテキストのimagefileフィールドにパスが保存されていた場合のIMGタグの記述例はリスト7-5-3の通りです。例えば、あるレコードで、imagefileフィールドが「products/pencil.jpg」だったとします。すると、「def.php?media=products/pencil.jpg」というURLがsrc属性に設定され、その時に画像を取得するリクエストが定義ファイルに対してクライアントから送られてきます。media=とあるのでmedia-root-dirの値と合成され、サーバー上では「/var/www/files/products/pencil.jpg」という絶対パスのファイルを開き、ファイルの内容とともにMIMEタイプを適切設定されたレスポンスがクライアントに返されます。その結果、サーバー上にある画像ファイルの内容が、クライアントにも見えるということです。このリクエストを送る時、media-handlingキーが設定されているので、サーバーから得られたコードをクッキーに乗せてサーバーに送り込み、サーバー側では自分が発行したものかを確かめて、そうであるならファイルの内容を返します。関係ないコードの場合には何も返しません。
<img src="def.php?media=" data-im="context@imagefile@#src" />
この章の最初に、ユーザー名やグループ名をフィールドに記述することで、そのレコードに対するアクセス権を指定したユーザーやグループにだけ許可する方法を『レコード単位のアクセス権』として説明しました。その特定のレコードに記録したパスのファイルを、特定のユーザーにだけ表示可能にすることもできます。なお、グループに対するアクセス権の設定はメディアデータではサポートしていません。
まず、定義ファイルの例は、リスト7-5-4の通りです。オプション設定側には、メディアデータのルートを指定するmedia-root-dirに加えて、media-contextキーの値も指定します。このキーは、どのコンテキストで、ユーザー単位のアクセス権を適用するのかを指定します。ここでは、contextコンテキストに設定したいので、値は「context」となっています。コンテキスト定義にはauthenticationキーがあり、メディアデータに対する認証を行うので、media-handlingキーの指定があります。また、userフィールドに設定されたユーザーに対してレコードのアクセス権をCRUDのすべての操作に割り当てるようにしています。
IM_Entry(
array(
array(
'name' => 'context',
'key' => 'id',
'authentication' => array(
'media-handling' => true,
'all' => array(
'target' => 'field-user',
'field' => 'user',
),
),
'protect-writing' => array( 'user' ),
),
),
array( // オプション設定
'media-root-dir' => '/var/www/files',
'media-context' => 'context',
'authentication' => array(
),
),
array('db-class' => 'PDO'),
false
);
そして、画像ファイルを取り出すIMG要素の例は、リスト7-5-5の通りです。レコード単位のアクセス権を設定するには、ファイル自体を規定されたディレクトリに入れる必要があります。例えば、ここでは、contextコンテキストのあるレコードについて、idフィールドは「120」、imagefileフィールドは「cutter.jpg」だったとします。以下のIMGタグの設定は、このファイルが「/var/www/files/context/id=120/cutter.jpg」である必要があります。つまり、media-root-dirと定義ファイルへのURLのmediaパラメーターの値を繋いだものがファイルのパスになるのは、単に認証を設定する場合と同じですが、そのファイルが、どのレコードに対するメディアデータなのかをパスで記録し、アクセス時には指定が必要です。つまり、「コンテキスト名/キーフィールド=その値」という相対パスを間に入れて、画像ファイル名を指定します。サーバー上でもこのようなディレクトリを用意して記録しておく必要があります。
<img src="defs.php?media=context/id=$/"
data-im="context@id@$src context@imagefile@#src" />
ここでは、contextコンテキストは、すべて、フィールドuserにログインしているユーザーと同じユーザー名がある場合のみに、データベースに対する処理が許可されています。メディアデータの読み込みの場合も同様ですが、リクエスト自体からアクセス許可されているかどうかを判定します。前の例だと、id=120のcontextコンテキストのレコードのuserフィールドと、ログインユーザー名が同じかどうかを確認します。ここで、他のユーザーに対するアクセス権があるパスを指定しても、userフィールドの値とログインユーザーは異なるため、メディアデータへのアクセスは拒否され、メディアデータ自体がユーザーのアクセス権を適用されていることになります。
このような「context/id=120/cutter.jpg」のようなパスを指定するのは厄介ではありますが、計算フィールド等を利用して、パスを生成してもいいでしょう。また、『5-4 JavaScriptコンポーネントの利用』で指定したファイルのアップロードコンポーネントは、ファイルを保存するときにコンテキスト名や主キーフィールドと値を絡めたパスを生成します。つまり、「自分でアップロードしたファイルは自分しか見えない」という動作を想定して機能を組み込んでいます。
演習レコード単位のアクセス権を設定する
同一のテーブル内で、レコードごとに参照できるユーザーを切り替えたい場合があります。その時、ユーザー名やグループ名を入力する文字型フィールドを用意して、そのフィールドをログインしているユーザー名で自動的に絞り込むという動作で実現しています。レコードを新規作成するときに、そのフィールドに自動設定されることなども確認しましょう。
最初のページ用の定義ファイルに必要な設定を行う
db-classは「PDO」のままでかまいません。dsnに「mysql:host=localhost;dbname=test_db;charset=utf8mb4」と入力します。そして、userに「web」、passwordに「password」と入力します。
db-classを「FileMaker_FX」に書き換えます。databaseは「TestDB」、userに「web」、passwordに「password」、serverに「192.168.56.1」、portに「80」、protocolに「http」、datatypeに「FMPro12」と入力します。
最初のページのページファイルの作成
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<script type="text/javascript" src="def25.php"></script>
</head>
<body>
<div id="IM_NAVIGATOR"></div>
<table style="float: left">
<thead>
<tr><th>id</th><th>作成日</th><th>タイトル</th><th>ユーザー</th></tr>
</thead>
<tbody>
<tr>
<td data-im="invoice@id"></td>
<td><input type="text" data-im="invoice@issued"/></td>
<td><input type="text" data-im="invoice@title"/></td>
<td data-im="invoice@authuser"></td>
</tr>
</tbody>
</table>
<table style="float: left; margin-left: 10px">
<thead>
<tr><th>id</th><th>作成日</th><th>タイトル</th><th>ユーザー</th></tr>
</thead>
<tbody>
<tr>
<td data-im="invoice-all@id"></td>
<td data-im="invoice-all@issued"></td>
<td data-im="invoice-all@title"></td>
<td data-im="invoice-all@authuser"></td>
</tr>
</tbody>
</table>
<br clear="all"/>
</body>
</html>
コンテキストにレコードを追加する
データベースへのリクエスト内容を確認する
ユーザー名のフィールドの更新を阻止する
<body>
<div id="IM_NAVIGATOR"></div>
<table style="float: left">
<thead>
<tr><th>id</th><th>作成日</th><th>タイトル</th><th>ユーザー</th></tr>
</thead>
<tbody>
<tr>
<td data-im="invoice@id"></td>
<td><input type="text" data-im="invoice@issued"/></td>
<td><input type="text" data-im="invoice@title"/></td>
<td><input type="text" data-im="invoice@authuser"/></td>
</tr>
</tbody>
</table>
演習のまとめ
- コンテキスト定義に設定を行うことで、レコード単位での参照や更新の可否を、ユーザーやグループに対して設定ができます。
- 参照時や更新、削除時には、ログインしているユーザー名であるものだけを絞り込む検索条件を付加します。新規レコード作成時には、自動的にログインしているユーザーの名前を指定したフィールドに設定します。
- ユーザー名のフィールドだけをページ側から更新できなくすることもできます。
このセクションのまとめ
ユーザー単位あるいはグループ単位にアクセス権を設定するテーブルの運用も可能です。ユーザー名やグループ名を記録するフィールドを用意し、定義ファイルのコンテキスト定義で、必要な設定を行います。メディアデータについても認証をかけることができます。なお、ユーザーによる制限のみがメディアデータに対しては可能です。