发布于 2015-08-27 16:45:01 | 277 次阅读 | 评论: 0 | 来源: 网络整理
In Symfony, you can check the permission to access data by using the ACL module, which is a bit overwhelming for many applications. A much easier solution is to work with custom voters, which are like simple conditional statements.
参见
Voters can also be used in other ways, like, for example, blacklisting IP addresses from the entire application: How to Implement your own Voter to Blacklist IP Addresses.
小技巧
Take a look at the authorization chapter for an even deeper understanding on voters.
In order to use voters, you have to understand how Symfony works with them.
All voters are called each time you use the isGranted()
method on Symfony’s
authorization checker (i.e. the security.authorization_checker
service). Each
one decides if the current user should have access to some resource.
Ultimately, Symfony uses one of three different approaches on what to do with the feedback from all voters: affirmative, consensus and unanimous.
For more information take a look at the section about access decision managers.
A custom voter must implement
VoterInterface
,
which has this structure:
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, the voter will check if the user has access to a specific
object according to your custom conditions (e.g. they must be the owner of
the object). If the condition fails, you’ll return
VoterInterface::ACCESS_DENIED
, otherwise you’ll return
VoterInterface::ACCESS_GRANTED
. In case the responsibility for this decision
does not belong to this voter, it will return VoterInterface::ACCESS_ABSTAIN
.
The goal is to create a voter that checks if a user has access to view or edit a particular object. Here’s an example implementation:
// src/AppBundle/Security/Authorization/Voter/PostVoter.php
namespace AppBundleSecurityAuthorizationVoter;
use SymfonyComponentSecurityCoreAuthorizationVoterVoterInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use SymfonyComponentSecurityCoreUserUserInterface;
class PostVoter implements VoterInterface
{
const VIEW = 'view';
const EDIT = 'edit';
public function supportsAttribute($attribute)
{
return in_array($attribute, array(
self::VIEW,
self::EDIT,
));
}
public function supportsClass($class)
{
$supportedClass = 'AppBundleEntityPost';
return $supportedClass === $class || is_subclass_of($class, $supportedClass);
}
/**
* @var AppBundleEntityPost $post
*/
public function vote(TokenInterface $token, $post, array $attributes)
{
// check if class of this object is supported by this voter
if (!$this->supportsClass(get_class($post))) {
return VoterInterface::ACCESS_ABSTAIN;
}
// check if the voter is used correct, only allow one attribute
// this isn't a requirement, it's just one easy way for you to
// design your voter
if (1 !== count($attributes)) {
throw new InvalidArgumentException(
'Only one attribute is allowed for VIEW or EDIT'
);
}
// set the attribute to check against
$attribute = $attributes[0];
// check if the given attribute is covered by this voter
if (!$this->supportsAttribute($attribute)) {
return VoterInterface::ACCESS_ABSTAIN;
}
// get current logged in user
$user = $token->getUser();
// make sure there is a user object (i.e. that the user is logged in)
if (!$user instanceof UserInterface) {
return VoterInterface::ACCESS_DENIED;
}
switch($attribute) {
case self::VIEW:
// the data object could have for example a method isPrivate()
// which checks the Boolean attribute $private
if (!$post->isPrivate()) {
return VoterInterface::ACCESS_GRANTED;
}
break;
case self::EDIT:
// we assume that our data object has a method getOwner() to
// get the current owner user entity for this data object
if ($user->getId() === $post->getOwner()->getId()) {
return VoterInterface::ACCESS_GRANTED;
}
break;
}
return VoterInterface::ACCESS_DENIED;
}
}
That’s it! The voter is done. The next step is to inject the voter into the security layer.
To inject the voter into the security layer, you must declare it as a service
and tag it with security.voter
:
# src/AppBundle/Resources/config/services.yml
services:
security.access.post_voter:
class: AppBundleSecurityAuthorizationVoterPostVoter
public: false
tags:
- { name: security.voter }
<!-- src/AppBundle/Resources/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="security.access.post_document_voter"
class="AppBundleSecurityAuthorizationVoterPostVoter"
public="false">
<tag name="security.voter" />
</service>
</services>
</container>
// src/AppBundle/Resources/config/services.php
$container
->register(
'security.access.post_document_voter',
'AppBundleSecurityAuthorizationVoterPostVoter'
)
->addTag('security.voter')
;
The registered voter will then always be asked as soon as the method isGranted()
from the authorization checker is called.
// src/AppBundle/Controller/PostController.php
namespace AppBundleController;
use SymfonyBundleFrameworkBundleControllerController;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentSecurityCoreExceptionAccessDeniedException;
class PostController extends Controller
{
public function showAction($id)
{
// get a Post instance
$post = ...;
// keep in mind, this will call all registered security voters
if (false === $this->get('security.authorization_checker')->isGranted('view', $post)) {
throw new AccessDeniedException('Unauthorised access!');
}
return new Response('<h1>'.$post->getName().'</h1>');
}
}
2.6 新版功能: The security.authorization_checker
service was introduced in Symfony 2.6. Prior
to Symfony 2.6, you had to use the isGranted()
method of the security.context
service.
It’s that easy!