Symfony2のFirewallの設定に関するメモ

Symfony2のSecurityコンポーネントではFirewallという仕組みを用いて認証/認可を行います。Symfony2ではおおまかにいうと次のようなフローで処理が進みます。

  1. Requestオブジェクトの初期化
  2. DIコンテナの起動
  3. ルーティングとセッションの初期化
  4. ルーティング情報を元にコントローラーの作成
  5. コントローラー(アクション)の実行
  6. Responseオブジェクトの送信

Firewallは上記の3と4の間に入ります。コントローラーの作成に入る前に、Requestオブジェクトの状態を見てアクセスを制御するのがFirewallの役目になります。

security.yml

Firewallはapp/config/security.ymlに次のようにして記述します。

security:
    providers:
        users:
            entity:
                class: CommmerceBundle:User

    firewalls:
        login:  
            pattern:  ^(/admin)?/login$
            security: false

        admin_area: 
            pattern: ^/admin
            form_login: 
                check_path: /admin/login_check
                login_path: /admin/login
            logout: 
                path: /admin/logout
                target: /admin/login

        customer_area:
            form_login: 
                check_path: /login_check
                login_path: /login
            logout: true
            anonymous: true

    access_control:
        - { path: ^(/admin)?/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
        - { path: ^/mypage, roles: ROLE_CUSTOMER, requires_channel: https }
        - { path: ^/admin, roles: ROLE_ADMIN, requires_channel: https }

上記はE-Commerceサイトを想定したsecurity.ymlです。このうちfirewallaccess_controlの2つがFirewallに該当する記述です。

セキュリティエリア

firewallsは主に認証の設定を行います。admin_areaやcustomer_areaはそれぞれセキュリティエリアの指定になります。認証はセキュリティエリア単位で行われ、正規表現を用いてURLのマッチングを行い、セキュリティエリアの特定を行います。上記の例では、/adminで始まるURLはadmin_area、それ以外はcustomer_areaとなります。ただし、/loginと/admin/loginは認証していなくてもアクセスしてよいので、別のエリアにしています。

エリアの設定

各エリアには次のような設定を記述します。

  • pattern
    • 指定した正規表現とURLがマッチしたエリアの設定が使用されます
  • security
    • falseを指定するとセキュリティの設定は無効になります。
  • form_login
    • 指定したログインフォーム画面を通じてエリアにログインできるようになります
  • logout
    • ログアウトのURLなどの指定を行います。
  • anonymous
    • trueを指定すると、認証されていない状態でエリア内にアクセスできるようになります。

この部分はSecurityBundleのコンフィギュレーションになりますが、SecurityFactoryという機構を用いて拡張することが可能です。

symfony 1のときはPHPのセッション単位でしか認証状態の管理ができなかったのですが、Symfony2からは柔軟な設定が可能になったわけです。まあそういうと聞こえはいいですけど、少し理解しづらいかも知れませんね。

form_login

form_loginを指定した場合ですが、自分ではフォーム画面を作成するだけで、認証部分のロジックを書く必要はありません。login_pathで指定したURLにログインフォーム画面のアクションを配置します。フォームのactionをcheck_pathで指定したパスにすると、後はうまいことやってくれます。

check_pathのものはルーティングを定義する必要こそありますが、コントローラーと対応させる必要はありません。routing.ymlに次の指定をするだけで構いません。これは、処理の順番としてルーティングが先に走りますが、コントローラーが呼び出される前にFirewallがinterceptするためです。これはlogoutの指定でも同様です。

_check_path:
    pattern: /login_check

認証されていない状態でどこかのセキュリティエリアにアクセスした場合、自動的にログイン画面に飛ばされますが、ログイン後は元々アクセスしようとしていた画面へ飛ぶようになっています。この挙動は設定で変更可能です。

anonymous

anonymousは匿名という意味ですが、Firewallを理解する上では非常に重要な設定となります。最初の例でECサイトを挙げましたが、ログインしなくても商品ページは閲覧できたりしますよね。セキュリティエリアを分けることで認証しないということも可能ですが、エリアを分けてしまうと認証状態も別になってしまい、ログインしていた場合でもユーザー情報を取得できなくなってしまいます。ログイン画面などで、ユーザー情報に一切アクセスしないとかであればよいのですが、ユーザー情報を取得したい場合の方が多いのではないかと思います。

そこで登場するのがanonymousです。anonymousをtrueにすると、ログインしていなくてもセキュリティエリア内へのアクセスが可能になります。anonymousをtrueにした場合は逆に、認証されていないと閲覧できない画面の制御を行う必要が出てきます。

access_control

access_controlは認可の設定です。権限(roles)を用いたアクセスの制御の指定を行います。ユーザーは最低限1つの権限を指定するようになっています。認証されている場合、恐らくROLE_USERのような権限を持つことになるでしょう(GitHub - FriendsOfSymfony/FOSUserBundle: Provides user management for your Symfony project. Compatible with Doctrine ORM & ODM, and custom storages.)。

認証している/いないという判定をするのではなく、なんの権限を持っているかという判定になります。変な言い方かも知れませんが、anonymousがtrueの場合は匿名ユーザーとして認証をパスすることができると考えてください。Firewallをくぐり抜けてきた段階で、認証のフェーズはパスしているのです。もっとも、内部的にはaccess_controlがFirewallの最期の壁なのでまだFirewallを抜けたわけではありませんが……。

権限を複数していすることももちろん可能なのですが、access_controlに権限を複数記述するのではなく、role_hierarchyという指定を行ってあげる必要があります。ここはsymfony 1のころのcredentialsとは少し違うので注意が必要です。

なお、匿名ユーザーでもよいという認可の指定にはIS_AUTHENTICATED_ANONYMOUSLYという権限を指定します。これは匿名ユーザー限定ではなく、匿名でもよいという指定なので注意してください。←この部分は間違っているかも。検証します・・・


Firewallの取り扱いは、anonymousとaccess_controlの使い方を把握しておく必要があります。それがわかってしまえば難しいことは(その部分の設定においては)ないです。

Securityコンポーネントは難しいですが、symfony 1のころの、security.ymlの状態に合わせてフィルターチェインの内容を動的に変化させて認証フローを挟むやり方も大概ですよね。

セキュリティの部分をデバッグするときは、Webプロファイラーで状態やイベントを確認したり、ログにデバッグ情報を細かく吐き出しているのでその当たりを参考にするとよいでしょう。ACL以外の構造はある程度把握できてきたので、質問があれば直接聞いていただいても結構ですよ。

2011.10.11 修正

s/IS_AUTHENTICATED_ANONYMOUS/IS_AUTHENTICATED_ANONYMOUSLY