在复杂的应用中,你可能经常面临访问权限决策不仅仅取决于请求者本人(Token)还牵涉到了被申请的对象的问题。这也是 ACLs 系统被制作出来的原因所在。
ACL的替代选择
使用ACL并不平凡,而对于较简单的情况下,他可能矫枉过正。如果你的许可逻辑可以通过一些简单的代码来完成(例如,检查博客是不是当前用户的)那么可以考虑使用voter。一个voter可以决定对象的访问,你能够使用它来完成复杂的决定,有效的实现自己的ACL。此外,强制授权(比如 isGranted 部分)就会看起来和你在本章所看到的这个条目极其相似,但是你的 voter 类就会在幕后控制判定逻辑了,而不是 ACL 系统。
想象一下,你正在设计一个博客系统,而看你文章的人可以评论它。现在,你希望用户能够编辑自己的评论,而不是其他用户的;此外,你自己希望能够编辑所有人的评论。在这种情况下,你要对comment域对象限制访问。使用symfony你可以用几种方式来做到这一点,其中两个基本的方式是(非详尽):
在你的逻辑方法中实现安全:基本上这意味着,评论最为参考,比对所有用户谁有权访问,并比对用户提供的Token。
角色来实现安全:这种方法是,为每一个评论对象添加一个角色,即ROLE_COMMENT_1,ROLE_COMMENT_2 等。
这两种方法都非常有效。然而,把你的验证代码和逻辑代码放到一起,会使他不能重用,并增加单元测试的难度。此外,如果很多用户会访问一个域对象,你的代码可能会遇到性能问题。
幸运的是,这里有一种更好的方式,你在下面就可以看到。
准备
现在,在你终于要才去行动之前,你需要做一些准备。首先,你需要配置ACL系统的连接以供使用:
1 2 3 4 5 6 |
# app/config/security.yml security: # ... acl: connection: default |
标注:这个ACL系统必须有一个链接,链接可以是Doctrine DBAL(默认使用)也可以是Doctrine MongoDB(用于MongoDBAclBundle)。然而,这并不意味着你必须使用Doctrine ORM或者ODM来映射你的域(domain)对象。你可以用你喜欢的任何方式来映射对象,比如 DoctrineORM,MongoDB ODM,Propel,rawSQL 等等。选择权在你手里。
在连接方式确定好之后,你必须导入数据机构。幸运的是,这个任务,只需要一行命令:
1 |
$ php bin/console init:acl |
开始入门
上面的场景,现在你就可以用acl去实现它。
一旦ACL被创建,你可以通过创建一个Access Control Entry(ACE)来巩固实体和你的用户之间的关系,来访问对象。
创建一个ACL并添加一个ACE
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 |
// src/AppBundle/Controller/BlogController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Acl\Domain\ObjectIdentity; use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; use Symfony\Component\Security\Acl\Permission\MaskBuilder; class BlogController extends Controller { // ... public function addCommentAction(Post $post) { $comment = new Comment(); // ... setup $form, and submit data if ($form->isValid()) { $entityManager = $this->getDoctrine()->getManager(); $entityManager->persist($comment); $entityManager->flush(); // creating the ACL $aclProvider = $this->get('security.acl.provider'); $objectIdentity = ObjectIdentity::fromDomainObject($comment); $acl = $aclProvider->createAcl($objectIdentity); // retrieving the security identity of the currently logged-in user $tokenStorage = $this->get('security.token_storage'); $user = $tokenStorage->getToken()->getUser(); $securityIdentity = UserSecurityIdentity::fromAccount($user); // grant owner access $acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER); $aclProvider->updateAcl($acl); } } } |
在这段代码中,有几个重点。在这里我着重讲两处:
首先,你可能会注意到->createAcl()没有直接传入域对象,但他实现了ObjectIdentityInterface。这个间接的步骤使你在没有实际的域对象实例时,也可以让ACL工作。这在你想要检查一大批对象的权限,将会起到很大的作用。
另一个有趣的部分就是调用->insertObjectAce()。在这个例子中,你允许当前登录用户去操作自己的评论。MaskBuilder::MASK_OWNER 是一个提前定义好的整数位掩码;不必担心掩码生成器会抽象大部分细节,但你能够在一个数据库行存储很多不同的权限,从性能上给予很多提升。
提示:ACEs 的检查顺序是很有意义的,作为一项通用的准则,你应该在最开始设立更多的入口。
访问检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// src/AppBundle/Controller/BlogController.php // ... class BlogController { // ... public function editCommentAction(Comment $comment) { $authorizationChecker = $this->get('security.authorization_checker'); // check for edit access if (false === $authorizationChecker->isGranted('EDIT', $comment)) { throw new AccessDeniedException(); } // ... retrieve actual comment object, and do your editing here } } |
在这个例子中,你检查用户是否有EDIT(编辑)权限。在内部,symfony映射权限到多个整数位掩码,并检查用户是否拥有这些码。
你可以建立32位的基本权限-位掩码(根据你操作系统php会有所不同在30-32之间)。此外,你还可以定义累加权限。
累加权限
在上面的第一个例子中,你只授予用户OWNER权限。尽管这样会使用户有效的执行任意的操作,例如查看,编辑域对象等。但在某些情况下,您可能想要明确的指定这些权限。
该MaskBuilder就能够很容易的创建这些位掩码,并把几个权限结合在一起:
1 2 3 4 5 6 7 8 |
$builder = new MaskBuilder(); $builder ->add('view') ->add('edit') ->add('delete') ->add('undelete') ; $mask = $builder->get(); // int(29) |
此整数位掩码可以被用来授予用户您在上面添加的基础权限:
1 2 |
$identity = new UserSecurityIdentity ( 'johannes' , 'AppBundle\Entity\User' ); $acl -> insertObjectAce ( $identity , $mask ); |
现在的用户可以查看,编辑,删除,并取消删除的对象。