概述 快速入门 教程 手册 最佳实践 组件 参考 贡献

发布于 2015-08-27 16:50:24 | 197 次阅读 | 评论: 0 | 来源: 网络整理

The Symfony Security component provides several layers to authorize users. One of the layers is called a “voter”. A voter is a dedicated class that checks if the user has the rights to connect to the application or access a specific resource/URL. For instance, Symfony provides a layer that checks if the user is fully authorized or if it has some expected roles.

It is sometimes useful to create a custom voter to handle a specific case not handled by the framework. In this section, you’ll learn how to create a voter that will allow you to blacklist users by their IP.

The Voter Interface

A custom voter must implement VoterInterface, which requires the following three methods:

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

The supportsAttribute() method is used to check if the voter supports the given user attribute (i.e: a role like ROLE_USER, an ACL EDIT, etc.).

The supportsClass() method is used to check if the voter supports the class of the object whose access is being checked.

The vote() method must implement the business logic that verifies whether or not the user has access. This method must return one of the following values:

  • VoterInterface::ACCESS_GRANTED: The authorization will be granted by this voter;
  • VoterInterface::ACCESS_ABSTAIN: The voter cannot decide if authorization should be granted;
  • VoterInterface::ACCESS_DENIED: The authorization will be denied by this voter.

In this example, you’ll check if the user’s IP address matches against a list of blacklisted addresses and “something” will be the application. If the user’s IP is blacklisted, you’ll return VoterInterface::ACCESS_DENIED, otherwise you’ll return VoterInterface::ACCESS_ABSTAIN as this voter’s purpose is only to deny access, not to grant access.

Creating a custom Voter

To blacklist a user based on its IP, you can use the request_stack service and compare the IP address against a set of blacklisted IP addresses:

// src/AppBundle/Security/Authorization/Voter/ClientIpVoter.php
namespace AppBundleSecurityAuthorizationVoter;

use SymfonyComponentHttpFoundationRequestStack;
use SymfonyComponentSecurityCoreAuthorizationVoterVoterInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;

class ClientIpVoter implements VoterInterface
{
    protected $requestStack;
    private $blacklistedIp;

    public function __construct(RequestStack $requestStack, array $blacklistedIp = array())
    {
        $this->requestStack  = $requestStack;
        $this->blacklistedIp = $blacklistedIp;
    }

    public function supportsAttribute($attribute)
    {
        // you won't check against a user attribute, so return true
        return true;
    }

    public function supportsClass($class)
    {
        // your voter supports all type of token classes, so return true
        return true;
    }

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

        return VoterInterface::ACCESS_ABSTAIN;
    }
}

That’s it! The voter is done. The next step is to inject the voter into the security layer. This can be done easily through the service container.

小技巧

Your implementation of the methods supportsAttribute() and supportsClass() are not being called internally by the framework. Once you have registered your voter the vote() method will always be called, regardless of whether or not these two methods return true. Therefore you need to call those methods in your implementation of the vote() method and return ACCESS_ABSTAIN if your voter does not support the class or attribute.

Declaring the Voter as a Service

To inject the voter into the security layer, you must declare it as a service, and tag it as a security.voter:

  • YAML
    # src/Acme/AcmeBundle/Resources/config/services.yml
    services:
        security.access.blacklist_voter:
            class:     AppBundleSecurityAuthorizationVoterClientIpVoter
            arguments: ["@request_stack", [123.123.123.123, 171.171.171.171]]
            public:    false
            tags:
                - { name: security.voter }
    
  • XML
    <!-- src/Acme/AcmeBundle/Resources/config/services.xml -->
    <service id="security.access.blacklist_voter"
             class="AppBundleSecurityAuthorizationVoterClientIpVoter" public="false">
        <argument type="service" id="request_stack" strict="false" />
        <argument type="collection">
            <argument>123.123.123.123</argument>
            <argument>171.171.171.171</argument>
        </argument>
        <tag name="security.voter" />
    </service>
    
  • PHP
    // src/Acme/AcmeBundle/Resources/config/services.php
    use SymfonyComponentDependencyInjectionDefinition;
    use SymfonyComponentDependencyInjectionReference;
    
    $definition = new Definition(
        'AppBundleSecurityAuthorizationVoterClientIpVoter',
        array(
            new Reference('request_stack'),
            array('123.123.123.123', '171.171.171.171'),
        ),
    );
    $definition->addTag('security.voter');
    $definition->setPublic(false);
    
    $container->setDefinition('security.access.blacklist_voter', $definition);
    

小技巧

Be sure to import this configuration file from your main application configuration file (e.g. app/config/config.yml). For more information see 使用 imports 导入. To read more about defining services in general, see the 服务容器(Service Container) chapter.

Changing the Access Decision Strategy

In order for the new voter to take effect, you need to change the default access decision strategy, which, by default, grants access if any voter grants access.

In this case, choose the unanimous strategy. Unlike the affirmative strategy (the default), with the unanimous strategy, if only one voter denies access (e.g. the ClientIpVoter), access is not granted to the end user.

To do that, override the default access_decision_manager section of your application configuration file with the following code.

  • YAML
    # app/config/security.yml
    security:
        access_decision_manager:
            # strategy can be: affirmative, unanimous or consensus
            strategy: unanimous
    
  • XML
    <!-- app/config/security.xml -->
    <config>
        <!-- strategy can be: affirmative, unanimous or consensus -->
        <access-decision-manager strategy="unanimous">
    </config>
    
  • PHP
    // app/config/security.xml
    $container->loadFromExtension('security', array(
        // strategy can be: affirmative, unanimous or consensus
        'access_decision_manager' => array(
            'strategy' => 'unanimous',
        ),
    ));
    

That’s it! Now, when deciding whether or not a user should have access, the new voter will deny access to any user in the list of blacklisted IPs.

Note that the voters are only called, if any access is actually checked. So you need at least something like

  • YAML
    # app/config/security.yml
    security:
        access_control:
            - { path: ^/, role: IS_AUTHENTICATED_ANONYMOUSLY }
    
  • XML
    <!-- app/config/security.xml -->
    <config>
        <access-control>
           <rule path="^/" role="IS_AUTHENTICATED_ANONYMOUSLY" />
        </access-control>
    </config>
    
  • PHP
    // app/config/security.xml
    $container->loadFromExtension('security', array(
        'access_control' => array(
           array('path' => '^/', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'),
        ),
     ));
    

参见

For a more advanced usage see Access Decision Manager.

最新网友评论  共有(0)条评论 发布评论 返回顶部

Copyright © 2007-2017 PHPERZ.COM All Rights Reserved   冀ICP备14009818号  版权声明  广告服务