How to Use Access Control Lists (ACLs)(类适于RBAC)
2016-03-10 11:04
507 查看
In complex applications, you will often face the problem that access decisions cannot only be based on the person (
who is requesting access, but also involve a domain object that access is being requested for. This is where the ACL system comes in.
Alternatives to ACLs
Using ACL's isn't trivial, and for simpler use cases, it may be overkill. If your permission logic could be described by just writing some code (e.g. to check if a Blog is owned by the current User),
then consider using voters. A voter is passed the object
being voted on, which you can use to make complex decisions and effectively implement your own ACL. Enforcing authorization (e.g. the
will look similar to what you see in this entry, but your voter class will handle the logic behind the scenes, instead of the ACL system.
Imagine you are designing a blog system where your users can comment on your posts. Now, you want a user to be able to edit their own comments, but not those of other users; besides, you yourself want to be able to edit all comments. In this scenario,
be the domain object that you want to restrict access to. You could take several approaches to accomplish this using Symfony, two basic approaches are (non-exhaustive):
Enforce security in your business methods: Basically, that means keeping a reference inside each
all users who have access, and then compare these users to the provided
Enforce security with roles: In this approach, you would add a role for each
i.e.
etc.
Both approaches are perfectly valid. However, they couple your authorization logic to your business code which makes it less reusable elsewhere, and also increases the difficulty of unit testing. Besides, you could run into performance issues if many users
would have access to a single domain object.
Fortunately, there is a better way, which you will find out about now.
Now, before you can finally get into action, you need to do some bootstrapping. First, you need to configure the connection the ACL system is supposed to use:
YAML
XML
PHP
The ACL system requires a connection from either Doctrine DBAL (usable by default) or Doctrine MongoDB (usable with MongoDBAclBundle).
However, that does not mean that you have to use Doctrine ORM or ODM for mapping your domain objects. You can use whatever mapper you like for your objects, be it Doctrine ORM, MongoDB ODM, Propel, raw SQL, etc. The choice is yours.
After the connection is configured, you have to import the database structure. Fortunately, there is a task for this. Simply run the following command:
Coming back to the small example from the beginning, you can now implement ACL for it.
Once the ACL is created, you can grant access to objects by creating an Access Control Entry (ACE) to solidify the relationship between the entity and your user.
There are a couple of important implementation decisions in this code snippet. For now, I only want to highlight two:
First, you may have noticed that
not accept domain objects directly, but only implementations of the
This additional step of indirection allows you to work with ACLs even when you have no actual domain object instance at hand. This will be extremely helpful if you want to check permissions for a large number of objects without actually hydrating these objects.
The other interesting part is the
In the example, you are granting the user who is currently logged in owner access to the Comment. The
a pre-defined integer bitmask; don't worry the mask builder will abstract away most of the technical details, but using this technique you can store many different permissions in one database row which gives a considerable boost in performance.
The order in which ACEs are checked is significant. As a general rule, you should place more specific entries at the beginning.
In this example, you check whether the user has the
Internally, Symfony maps the permission to several integer bitmasks, and checks whether the user has any of them.
You can define up to 32 base permissions (depending on your OS PHP might vary between 30 to 32). In addition, you can also define cumulative permissions.
In the first example above, you only granted the user the
permission. While this effectively also allows the user to perform any operation such as view, edit, etc. on the domain object, there are cases where you may want to grant these permissions explicitly.
The
bit masks easily by combining several base permissions:
This integer bitmask can then be used to grant a user the base permissions you added above:
The user is now allowed to view, edit, delete, and un-delete objects.
Token)
who is requesting access, but also involve a domain object that access is being requested for. This is where the ACL system comes in.
Alternatives to ACLs
Using ACL's isn't trivial, and for simpler use cases, it may be overkill. If your permission logic could be described by just writing some code (e.g. to check if a Blog is owned by the current User),
then consider using voters. A voter is passed the object
being voted on, which you can use to make complex decisions and effectively implement your own ACL. Enforcing authorization (e.g. the
isGrantedpart)
will look similar to what you see in this entry, but your voter class will handle the logic behind the scenes, instead of the ACL system.
Imagine you are designing a blog system where your users can comment on your posts. Now, you want a user to be able to edit their own comments, but not those of other users; besides, you yourself want to be able to edit all comments. In this scenario,
Commentwould
be the domain object that you want to restrict access to. You could take several approaches to accomplish this using Symfony, two basic approaches are (non-exhaustive):
Enforce security in your business methods: Basically, that means keeping a reference inside each
Commentto
all users who have access, and then compare these users to the provided
Token.
Enforce security with roles: In this approach, you would add a role for each
Commentobject,
i.e.
ROLE_COMMENT_1,
ROLE_COMMENT_2,
etc.
Both approaches are perfectly valid. However, they couple your authorization logic to your business code which makes it less reusable elsewhere, and also increases the difficulty of unit testing. Besides, you could run into performance issues if many users
would have access to a single domain object.
Fortunately, there is a better way, which you will find out about now.
Bootstrapping¶
Now, before you can finally get into action, you need to do some bootstrapping. First, you need to configure the connection the ACL system is supposed to use:YAML
1 2 3 4 5 6 | # app/config/security.yml security: # ... acl: connection: default |
PHP
The ACL system requires a connection from either Doctrine DBAL (usable by default) or Doctrine MongoDB (usable with MongoDBAclBundle).
However, that does not mean that you have to use Doctrine ORM or ODM for mapping your domain objects. You can use whatever mapper you like for your objects, be it Doctrine ORM, MongoDB ODM, Propel, raw SQL, etc. The choice is yours.
After the connection is configured, you have to import the database structure. Fortunately, there is a task for this. Simply run the following command:
1 | $ php app/console init:acl |
Getting Started¶
Coming back to the small example from the beginning, you can now implement ACL for it.Once the ACL is created, you can grant access to objects by creating an Access Control Entry (ACE) to solidify the relationship between the entity and your user.
Creating an ACL and Adding an ACE¶
1 2 3 4 5 6 | // 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); } } } |
First, you may have noticed that
->createAcl()does
not accept domain objects directly, but only implementations of the
ObjectIdentityInterface.
This additional step of indirection allows you to work with ACLs even when you have no actual domain object instance at hand. This will be extremely helpful if you want to check permissions for a large number of objects without actually hydrating these objects.
The other interesting part is the
->insertObjectAce()call.
In the example, you are granting the user who is currently logged in owner access to the Comment. The
MaskBuilder::MASK_OWNERis
a pre-defined integer bitmask; don't worry the mask builder will abstract away most of the technical details, but using this technique you can store many different permissions in one database row which gives a considerable boost in performance.
The order in which ACEs are checked is significant. As a general rule, you should place more specific entries at the beginning.
Checking Access¶
1 2 3 4 5 6 | // 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 } } |
EDITpermission.
Internally, Symfony maps the permission to several integer bitmasks, and checks whether the user has any of them.
You can define up to 32 base permissions (depending on your OS PHP might vary between 30 to 32). In addition, you can also define cumulative permissions.
Cumulative Permissions¶
In the first example above, you only granted the user the OWNERbase
permission. While this effectively also allows the user to perform any operation such as view, edit, etc. on the domain object, there are cases where you may want to grant these permissions explicitly.
The
MaskBuildercan be used for creating
bit masks easily by combining several base permissions:
1 2 3 4 5 6 | $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); |
相关文章推荐
- 计算机网络之物理层
- linux+QT实现文件夹拷贝并附带进度功能
- imageNamed/ imageWithContentsOfFile /imageWithData 的区别
- TF-IDF与余弦相似性的应用(一):自动提取关键词
- Spring 学习笔记
- USB设备---URB请求快
- Android Context 上下文 你必须知道的一切
- 理解和使用 JavaScript 中的回调函数
- Verilog-2001新增特性
- Java HashMap工作原理及实现
- 炸弹人游戏_暴力枚举
- oracle数据误删恢复
- ALGO-86 矩阵乘法
- 在Hibernate中Query的uniqueResult查询和Result查询有什么区别
- 限制EditText 输入的字节数
- 从零开始学Linux[二]:日常操作
- PHP图片压缩
- centos6.5搭建GIT服务器
- [置顶] 学习JAVA之路(一、概述)
- 如何正确合理的建立MYSQL数据库索引