Security Voters提供了一种架构,用于在Symfony程序中设置细粒化的访问限制。相对于ACL,其主要优势在于设置和使用的难度低了一个数量级。
之前的Symfony版本中,voter实现的是VoterInterface
接口,具有下述特征:
1 2 3 4 5 6 | interface VoterInterface
{
public function supportsAttribute($attribute);
public function supportsClass($class);
public function vote(TokenInterface $token, $object, array $attributes);
} |
实现这个接口并不难,但却导致了代码过度膨胀,举例来说,下面83行代码仅仅为了定义一个简单voter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | // src/Acme/DemoBundle/Security/Authorization/Voter/PostVoter.php
namespace Acme\DemoBundle\Security\Authorization\Voter;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
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 = 'Acme\DemoBundle\Entity\Post';
return $supportedClass === $class || is_subclass_of($class, $supportedClass);
}
/**
* @var \Acme\DemoBundle\Entity\Post $post
*/
public function vote(TokenInterface $token, $post, array $attributes)
{
// check if class of this object is supported by this voter 检查对象的类是否被voter支持
if (!$this->supportsClass(get_class($post))) {
return VoterInterface::ACCESS_ABSTAIN;
}
// check if the voter is used correct, only allow one attribute 检查voter的正确使用,只允许1个属性
// this isn't a requirement, it's just one easy way for you to
// design your voter 这并非必须,只是个易用方式,供你打造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 检查给定属性是否被本voter覆盖
if (!$this->supportsAttribute($attribute)) {
return VoterInterface::ACCESS_ABSTAIN;
}
// get current logged in user 取得user信息
$user = $token->getUser();
// make sure there is a user object (i.e. that the user is logged in) 确保是user对象
if (!$user instanceof UserInterface) {
return VoterInterface::ACCESS_DENIED;
}
switch($attribute) {
case self::VIEW:
// the data object could have for example a method isPrivate() 假定数据对象有一个isPrivate()方法
// which checks the Boolean attribute $private 用于检查布尔值的属笥$private
if (!$post->isPrivate()) {
return VoterInterface::ACCESS_GRANTED;
}
break;
case self::EDIT:
// we assume that our data object has a method getOwner() to 假设数据对象有个getOwner()方法
// get the current owner user entity for this data object 得到当前数据对象的拥有者entity
if ($user->getId() === $post->getOwner()->getId()) {
return VoterInterface::ACCESS_GRANTED;
}
break;
}
return VoterInterface::ACCESS_DENIED;
}
} |
根据DX开发体验的讨论结果,Symfony 2.6将允许定义简化security voters。为了实现简化效果,请使用全新AbstractVoter
抽象类,它实现了VoterInterface
接口,并定义了如下方法:
1 2 3 4 5 6 7 8 9 | abstract class AbstractVoter implements VoterInterface
{
public function supportsAttribute($attribute);
public function supportsClass($class);
public function vote(TokenInterface $token, $object, array $attributes);
abstract protected function getSupportedClasses();
abstract protected function getSupportedAttributes();
abstract protected function isGranted($attribute, $object, $user = null);
} |
supportsAttribute()
、supportsClass()
和vote()
三个方法,帮你减少了voter的样板式代码,令你集中火力到程序的逻辑上。因此,上面的voter现在只需41行代码即可实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | // src/Acme/DemoBundle/Security/Authorization/Voter/PostVoter.php
namespace Acme\DemoBundle\Security\Authorization\Voter;
use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
use Symfony\Component\Security\Core\User\UserInterface;
class PostVoter extends AbstractVoter
{
const VIEW = 'view';
const EDIT = 'edit';
protected function getSupportedAttributes()
{
return array(self::VIEW, self::EDIT);
}
protected function getSupportedClasses()
{
return array('Acme\DemoBundle\Entity\Post');
}
protected function isGranted($attribute, $post, $user = null)
{
// make sure there is a user object (i.e. that the user is logged in)
if (!$user instanceof UserInterface) { //确保是User对象(该用户已经登陆)
return false;
}
// custom business logic to decide if the given user can view
// and/or edit the given post 自定义商务逻辑来决定当前用户是否能看到/编辑内容
if ($attribute == self::VIEW && !$post->isPrivate()) {
return true;
}
if ($attribute == self::EDIT && $user->getId() === $post->getOwner()->getId()) {
return true;
}
return false;
}
} |
少写了代码,却得到相同效果,于是推动了你的生产力。正因为DX开发体验的引入,这将是我们永不放弃的追求。Symfony 2.6是首个强调此哲学的版本。