.. index::
   single: Security, Voters

IPアドレスのブラックリストの独自 Voter の実装方法
=================================================

Symfony2 のセキュリティコンポーネントは、ユーザ認証のための複数のレイヤーを用意しています。 `voter` と呼ばれるレイヤーは、その1つです。 voter はユーザがアプリケーションに接続できる権利があるかのチェックを行うクラスです。例えば、 Symfony2 は、ユーザが完全に認証されているか、また、必要な権限を保持しているかといったことをチェックするレイヤーを提供します。

フレームワークによる処理ではなく、特定のケースを処理するためのカスタム化された voter が役に立つこともあります。このセクションでは、 IP アドレスに基づきユーザをブラックリストに入れるための voter の作り方を学びましょう

Voter インタフェース
--------------------

カスタム Voter は、次の3つのメソッドを必要とする :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface` インタフェースを実装する必要があります:

.. code-block:: php

    interface VoterInterface
    {
        function supportsAttribute($attribute);
        function supportsClass($class);
        function vote(TokenInterface $token, $object, array $attributes);
    }


``supportsAttribute()`` メソッドは、 voter が権限や ACL といったユーザの属性をサポートするかチェックします。

``supportsClass()`` メソッドは、 voter が現在のユーザのトークンクラスをサポートするかチェックします。

``vote()`` メソッドは、ビジネスロジックを実装する必要があり、そこでユーザがアクセス可能か証明します。このメソッドは、次の値のいずれかを返す必要があります。

* ``VoterInterface::ACCESS_GRANTED``: ユーザがアプリケーションにアクセス可能である
* ``VoterInterface::ACCESS_ABSTAIN``: voter では、 ユーザがアクセス可能か否かが判断できない
* ``VoterInterface::ACCESS_DENIED``: ユーザがアプリケーションにアクセス不可能である

この例では、ユーザの IP アドレスがブラックリストのアドレス群を調べます。ユーザの IP がブラックリスト内にあれば、 ``VoterInterface::ACCESS_DENIED`` を返し、そうでなければ、 ``VoterInterface::ACCESS_ABSTAIN`` を返します。つまり、この voter の目的は、実際のアクセスを与えることではなく、アクセスの拒否のみとします。

カスタム Voter の作成方法
-------------------------

ユーザを IP に基づいてブラックリストに入れるには、 ``request`` サービスを使用してブラックリストの IP アドレスの集合と比較します。

.. code-block:: php

    namespace Acme\DemoBundle\Security\Authorization\Voter;

    use Symfony\Component\DependencyInjection\ContainerInterface;
    use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

    class ClientIpVoter implements VoterInterface
    {
        public function __construct(ContainerInterface $container, array $blacklistedIp = array())
        {
            $this->container     = $container;
            $this->blacklistedIp = $blacklistedIp;
        }

        public function supportsAttribute($attribute)
        {
            // ユーザ属性は調べないので、 true を返します
            return true;
        }

        public function supportsClass($class)
        {
            // voter はトークンクラスの全てをサポートするので、 true を返します
            return true;
        }

        function vote(TokenInterface $token, $object, array $attributes)
        {
            $request = $this->container->get('request');
            if (in_array($this->request->getClientIp(), $this->blacklistedIp)) {
                return VoterInterface::ACCESS_DENIED;
            }

            return VoterInterface::ACCESS_ABSTAIN;
        }
    }

これで voter ができました。次のステップは、 voter をセキュリティレイヤーに注入する(inject)ことです。これは、サービスコンテナを介して簡単に行うことができます。

Voter をサービスとして宣言する
------------------------------

Voter をセキュリティレイヤーに注入する(inject)には、 Voter をサービスとして宣言して、 "security.voter" としてタグ付けする必要があります。

.. configuration-block::

    .. code-block:: yaml

        # src/Acme/AcmeBundle/Resources/config/services.yml

        services:
            security.access.blacklist_voter:
                class:      Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter
                arguments:  [@service_container, [123.123.123.123, 171.171.171.171]]
                public:     false
                tags:
                    -       { name: security.voter }

    .. code-block:: xml

        <!-- src/Acme/AcmeBundle/Resources/config/services.xml -->

        <service id="security.access.blacklist_voter"
                 class="Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter" public="false">
            <argument type="service" id="service_container" strict="false" />
            <argument type="collection">
                <argument>123.123.123.123</argument>
                <argument>171.171.171.171</argument>
            </argument>
            <tag name="security.voter" />
        </service>

    .. code-block:: php

        // src/Acme/AcmeBundle/Resources/config/services.php

        use Symfony\Component\DependencyInjection\Definition;
        use Symfony\Component\DependencyInjection\Reference;

        $definition = new Definition(
            'Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter',
            array(
                new Reference('service_container'),
                array('123.123.123.123', '171.171.171.171'),
            ),
        );
        $definition->addTag('security.voter');
        $definition->setPublic(false);

        $container->setDefinition('security.access.blacklist_voter', $definition);

.. tip::

   このコンフィギュレーションファイルをメインのアプリケーションファイル( ``app/config/config.yml`` など)からインポートすることを忘れないでください。詳細は、 :ref:`service-container-imports-directive` を参照してください。より一般的なサービスの定義については、ドキュメントの :doc:`/book/service_container` 章を参照してください。

アクセス可否の決定戦略を変更する
--------------------------------

新しい voter の効力を有効にするために、デフォルトのアクセス決定戦略を変更する必要があります。デフォルトでは、 *いずれかの* voter がアクセスを許可していれば、良いことになっています。

ここでのケースでは、 ``unanimous`` 戦略を選択しましょう。デフォルトの ``affirmative`` 戦略と異なり、 ``unanimous`` 戦略は voter 1つでもアクセスを拒否すれば(例えば ``ClientIpVoter`` )、ユーザにアクセスが許可されません。

アプリケーションのコンフィギュレーションファイルの ``access_decision_manager`` セクションをデフォルト値から次のようにオーバーライドしましょう。

.. configuration-block::

    .. code-block:: yaml

        # app/config/security.yml
        security:
            access_decision_manager:
                # Strategy can be: affirmative, unanimous or consensus
                strategy: unanimous

できました。これで、ユーザがアクセスがあるかどうかを決定する歳に、新しい Voter はブラックリスト IP リストに入っているユーザを全てアクセス拒否するようになりました。

.. 2011/11/17 ganchiku cdde9366687364639706b50e1440ac962a696b75