セッション管理のための機能セットを提供します。具体的には、レイジーセッションスタート、セッションセグメント、次のリクエストまでしか有効ではない「フラッシュ」値、およびCSRF対策のツールです。
aura/session
バージョン 2.0.*@beta
をインストールします。
composer.json
に下記を追加します。
インストールを実行します。
Aura.Session には、Aura\Session\Session のオブジェクトである aura/session:session
サービスが最初から用意されています。
このサービスをレスポンダーやビューヘルパーに注入することで、Aura\Session\Session オブジェクトを使うことができます。
PHP標準の機能で、セッションの値は $_SESSION
配列によって維持されます。
しかし、さまざまなライブラリやプロジェクトが同じキー群に対して書き換えを行おうとした場合に、衝突が発生し、予期しない振る舞いを引き起こすことになってしまうでしょう。
この問題を解決するために、 Segment (セグメント)オブジェクトを使います。
各 Segment は、名前を付けたキーを使って $_SESSION
配列内部にアクセスします。これにより衝突が回避されます。
たとえば、Vendor\Package\ClassName
で Segment を使うのであれば、 $_SESSION['Vendor\Package\ClassName']
への参照が使われることになります。
Segment で set()
したり get()
したりすると、この参照への操作によってセッション配列の値が維持されます。
セッションセグメントの利点は、スーパーグローバル変数 $_SESSION
でキー名の衝突が避けられることです。キーにはクラス名(もしくは何らかのユニークな名前)をセグメント名として使います。
セグメントを使えば、さまざまなパッケージがスーパーグローバル変数 $_SESSION
を扱っていても、お互いが干渉し合ってしまうようなことはありません。
Segment の値をすべて削除するには、clear()
メソッドを使います。
session_start()
をまだ 呼び出さずに、単に Session マネージャのインスタンス作成や、 Segment の取得だけを行うことができます。
session_start()
は、下記のような特定の状況下においてだけ実行されるのです。
Segment の読み込み時(例:get()
)、 Session はセッションCookieがすでに設定されているかどうかを判定します。設定済みであった場合は、session_start()
を前回開始したセッションを再開することで呼び出します。
セッションCookieがまだ設定されていない場合は、$_SESSION
変数の値がまだ無いということなので、 session_start()
の呼び出しを行いません。
Segment の書き込み時(例:set()
)、Session は必ず session_start()
を呼び出します。
前回セッションが存在している場合には再開し、存在していなかった場合には新しいセッションを開始します。
つまり、各 Segment を任意のタイミングで作成して良いというわけです。
Segment で決まった手順で実際にやり取りが発生するまでの間は、session_start()
が呼ばれることはないのです。セッション開始に関わるリソースを効率良く使っていると言えます。
もちろん、セッションの開始を強制的に行うこともできますし、Session start()
メソッドを呼ぶことでセッションを再開させることもできます。
しかし、そのやり方だと、セッションレイジーロード方式のもともとの目的を達成することはできません。
以下のメソッドは、セグメントを使った全てのセッションデータ、およびフラッシュに対して使います
セッションデータを保存して、以降は現在のリクエストで使わないようにするには、Session マネージャで commit()
メソッドを呼び出します。
commit()
メソッドはsession_write_close()
に相当します。 セッションのコミットをしないと、後からセッションを続ける際にセッションの値が利用できません。
現在のリクエストでセッションを張ったまま、全セッションデータをクリアするには、Session マネージャで clear()
メソッドを使います。
すべてのフラッシュ値をクリアするには、clearFlash()
メソッドを使います。
データをクリアして、かつ以降のリクエストでセッションを終了させるには、つまり、完全に破棄するには、 destroy()
メソッドを呼び出します。
destroy()
を呼ぶと、setcookie()
を通じてセッションCookieも併せて削除されます。
Cookie 削除をほかのやり方で行うには、SessionFactory のメソッド newInstance()
の第2引数に callable を渡します。
callable は3つのパラメータを取ります。Cookieの名前、パスと、ドメインです。
ユーザの権限を変更した時、たとえば、システムへのアクセス権限を付与したり剥奪したりした場合には、必ずセッションIDを再生成しなければなりません。
regenerateId()
メソッドは、CSRF トークン値の再生成も兼ねます。
「クロスサイトリクエストフォージェリ」は、悪意あるJavaScript等を使ったセキュリティ上の攻撃手法です。 認証済みとなっているユーザのサーバ宛てに、クライアントブラウザが裏でリクエストを送信させられます。 リクエストは妥当であるかのよう 見える ものの、そうではなくて、強要されたものです。 ユーザは実際にはリクエストを行っておらず、悪意あるJavaScriptが行っているのです。
CSRF攻撃を防ぐには、サーバサイドのロジックを下記のようにする必要があります。
ユニークなトークン値を認証済みユーザのセッションに対して発行し、フォームに埋め込む。
すべてのPOST/PUT/DELETE(すなわち、危険な)リクエストについて、トークン値が含まれていることを確認する。
アプリケーションが GET リクエストをリソースの変更に使う場合は(そもそも不適切なGETの使い方ではありますが)、 認証済みユーザによる GET リクエストについてもCSRFの検証をするべきです。
下記の例では、フォームフィールド名が __csrf_value
となります。
CSRF攻撃から防御したいすべてのフォームにおいて、セッションのCSRFトークン値をフォームのフィールドに埋め込みます。
リクエストを処理する時に、入力されてきたCSRFトークンが妥当であるかどうかを認証済みユーザについて検証します。
CSRFトークンを利用するには、ランダムで暗号学的にセキュアな値を得る必要があります。
mt_rand()
のような仕組みを使うのでは不適切です。
Aura.Session には、 RandvalInterface
の実装である Randval
クラスが付属していて、
openssl
か mcrypt
拡張のどちらかをランダム値の生成に使います。
もしこれらの拡張をインストールしていないのであれば、RandvalInterface
を実装して自前でランダム値を組む必要があるでしょう。
私たちはRandomLibのラッパを作ることを推奨します。
Segment 値はセッションがクリアまたは破棄されるまでの間は保存されています。 しかし、値の設定が次回リクエストまでの間だけ伝わって、それ以降は破棄される方が都合が良いケースもあります。 これは「フラッシュ」値と呼ばれます。
Segment にフラッシュ値を設定するには、 setFlash()
メソッドを使います。
そして、次のリクエストでは、 getFlash()
を使ってフラッシュ値を読み込みます。
get()
では、フラッシュキーが存在しなかった場合、代わりの値を取得することができます。 たとえば、getFlash('foo', 'not set')
は、もし ‘foo’ キーが利用可能でなければ ‘not set’ を返します。
setFlash()
を使うと、現在のリクエストではなく 次の リクエストでだけ、フラッシュ値が利用可能となります。
次回リクエストだけでなく今すぐフラッシュ値を作成したいのであれば、 setFlashNow($key, $val)
を使います。
getFlash()
を使うと、前回リクエストで設定された現在利用可能な値だけが返されます。
値を次のリクエストで利用できるように読み込むには、 getFlashNext($key, $alt)
を使います。
フラッシュ値を現在のリクエストから次のリクエストへ維持させたいこともあるでしょう。
セグメント単位で維持するには、 Segment の keepFlash()
メソッドを呼び出します。
すべてのセグメントの全フラッシュ値を維持するには、 Session の keepFlash()
メソッドを呼び出します。
同様に、フラッシュ値をセグメント単位、またはセッション全体でクリアすることができます。
clearFlash()
メソッドは、 Segment ではセグメントに対してだけクリアを行い、同じメソッド名で Session ではすべてのセグメントの全フラッシュ値をクリアします。