1. Introduction
1.介绍
Access Control List (ACL) is a list of permissions attached to an object. An ACL specifies which identities are granted which operations on a given object.
访问控制列表(ACL)是附加到一个对象的权限列表。一个ACL指定了哪些身份被授予对特定对象的哪些操作。
Spring Security Access Control List is a Spring component which supports Domain Object Security. Simply put, Spring ACL helps in defining permissions for specific user/role on a single domain object – instead of across the board, at the typical per-operation level.
Spring Security Access Control List是一个Spring组件,它支持域对象安全。简单地说,Spring ACL有助于为单个域对象上的特定用户/角色定义权限–而不是在典型的每个操作级别上全面地定义权限。
For example, a user with the role Admin can see (READ) and edit (WRITE) all messages on a Central Notice Box, but the normal user only can see messages, relate to them and cannot edit. Meanwhile, others user with the role Editor can see and edit some specific messages.
例如,具有Admin角色的用户可以看到(READ)和编辑(WRITE)一个中央通知箱上的所有信息,但普通用户只能看到信息,与它们相关,不能编辑。同时,其他具有编辑角色的用户可以看到和编辑一些特定的信息。
Hence, different user/role has different permission for each specific object. In this case, Spring ACL is capable of achieving the task. We’ll explore how to set up basic permission checking with Spring ACL in this article.
因此,不同的用户/角色对每个特定对象有不同的权限。在这种情况下,Spring ACL能够实现这一任务。我们将在本文中探讨如何用Spring ACL设置基本的权限检查。
2. Configuration
2.配置
2.1. ACL Database
2.1.ACL数据库
To use Spring Security ACL, we need to create four mandatory tables in our database.
为了使用Spring Security ACL,我们需要在数据库中创建四个强制性表。
The first table is ACL_CLASS, which store class name of the domain object, columns include:
第一个表是ACL_CLASS,它存储域对象的类名,列包括。
- ID
- CLASS: the class name of secured domain objects, for example: com.baeldung.acl.persistence.entity.NoticeMessage
Secondly, we need the ACL_SID table which allows us to universally identify any principle or authority in the system. The table needs:
其次,我们需要ACL_SID表,它允许我们普遍地识别系统中的任何原则或权力。该表需要。
- ID
- SID: which is the username or role name. SID stands for Security Identity
- PRINCIPAL: 0 or 1, to indicate that the corresponding SID is a principal (user, such as mary, mike, jack…) or an authority (role, such as ROLE_ADMIN, ROLE_USER, ROLE_EDITOR…)
Next table is ACL_OBJECT_IDENTITY, which stores information for each unique domain object:
下一个表是ACL_OBJECT_IDENTITY,它存储每个独特的域对象的信息。
- ID
- OBJECT_ID_CLASS: define the domain object class, links to ACL_CLASS table
- OBJECT_ID_IDENTITY: domain objects can be stored in many tables depending on the class. Hence, this field store the target object primary key
- PARENT_OBJECT: specify parent of this Object Identity within this table
- OWNER_SID: ID of the object owner, links to ACL_SID table
- ENTRIES_INHERITING: whether ACL Entries of this object inherits from the parent object (ACL Entries are defined in ACL_ENTRY table)
Finally, the ACL_ENTRY store individual permission assigns to each SID on an Object Identity:
最后,ACL_ENTRY存储个人权限分配给Object Identity上的每个SID。
- ID
- ACL_OBJECT_IDENTITY: specify the object identity, links to ACL_OBJECT_IDENTITY table
- ACE_ORDER: the order of current entry in the ACL entries list of corresponding Object Identity
- SID: the target SID which the permission is granted to or denied from, links to ACL_SID table
- MASK: the integer bit mask that represents the actual permission being granted or denied
- GRANTING: value 1 means granting, value 0 means denying
- AUDIT_SUCCESS and AUDIT_FAILURE: for auditing purpose
2.2. Dependency
2.2.依赖性
To be able to use Spring ACL in our project, let’s first define our dependencies:
为了能够在我们的项目中使用Spring ACL ,让我们首先定义我们的依赖性。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
Spring ACL requires a cache to store Object Identity and ACL entries, so we’ll make use of Ehcache here. And, to support Ehcache in Spring, we also need the spring-context-support.
Spring ACL需要一个缓存来存储Object Identity和ACL条目,所以我们将在这里使用Ehcache。而且,为了在Spring中支持Ehcache,我们还需要spring-context-support。
When not working with Spring Boot, we need to add versions explicitly. Those can be checked on Maven Central: spring-security-acl, spring-security-config, spring-context-support, ehcache-core.
在不使用Spring Boot时,我们需要明确添加版本。这些可以在Maven中心检查。spring-security-acl、spring-security-config、spring-context-support、ehcache-core。
2.3. ACL-Related Configuration
2.3.与ACL相关的配置
We need to secure all methods which return secured domain objects, or make changes to the object, by enabling Global Method Security:
我们需要通过启用全球方法安全:来保护所有返回安全域对象或对对象进行更改的方法。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class AclMethodSecurityConfiguration
extends GlobalMethodSecurityConfiguration {
@Autowired
MethodSecurityExpressionHandler
defaultMethodSecurityExpressionHandler;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return defaultMethodSecurityExpressionHandler;
}
}
Let’s also enable Expression-Based Access Control by setting prePostEnabled to true to use Spring Expression Language (SpEL). Moreover, we need an expression handler with ACL support:
让我们也通过设置 prePostEnabled 到 true来启用Spring Expression Language (SpEL)的基于表达式的访问控制。此外,我们需要一个支持ACL的表达式处理器。
@Bean
public MethodSecurityExpressionHandler
defaultMethodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler
= new DefaultMethodSecurityExpressionHandler();
AclPermissionEvaluator permissionEvaluator
= new AclPermissionEvaluator(aclService());
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
Hence, we assign AclPermissionEvaluator to the DefaultMethodSecurityExpressionHandler. The evaluator needs a MutableAclService to load permission settings and domain object’s definitions from the database.
因此,我们将AclPermissionEvaluator分配给DefaultMethodSecurityExpressionHandler。评估器需要一个MutableAclService来从数据库加载权限设置和域对象的定义。
For simplicity, we use the provided JdbcMutableAclService:
为了简单起见,我们使用提供的JdbcMutableAclService。
@Bean
public JdbcMutableAclService aclService() {
return new JdbcMutableAclService(
dataSource, lookupStrategy(), aclCache());
}
As its name, the JdbcMutableAclService uses JDBCTemplate to simplify database access. It needs a DataSource (for JDBCTemplate), LookupStrategy (provides an optimized lookup when querying the database), and an AclCache (caching ACL Entries and Object Identity).
正如它的名字,JdbcMutableAclService使用JDBCTemplate来简化数据库访问。它需要一个数据源(用于JDBCTemplate),查找策略(在查询数据库时提供优化的查找),以及一个AclCache(缓存ACL条目和对象身份)。
Again, for simplicity, we use provided BasicLookupStrategy and EhCacheBasedAclCache.
同样,为了简单起见,我们使用提供的BasicLookupStrategy和EhCacheBasedAclCache。
@Autowired
DataSource dataSource;
@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_ADMIN"));
}
@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(
new ConsoleAuditLogger());
}
@Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(
aclEhCacheFactoryBean().getObject(),
permissionGrantingStrategy(),
aclAuthorizationStrategy()
);
}
@Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
@Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
@Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(
dataSource,
aclCache(),
aclAuthorizationStrategy(),
new ConsoleAuditLogger()
);
}
Here, the AclAuthorizationStrategy is in charge of concluding whether a current user possesses all required permissions on certain objects or not.
在这里,AclAuthorizationStrategy负责断定当前用户是否拥有某些对象的所有必要权限。
It needs the support of PermissionGrantingStrategy, which defines the logic for determining whether a permission is granted to a particular SID.
它需要PermissionGrantingStrategy的支持,它定义了确定是否向特定SID授予权限的逻辑。
3. Method Security With Spring ACL
3.使用Spring ACL的方法安全
So far, we’ve done all necessary configuration. Now we can put required checking rule on our secured methods.
到目前为止,我们已经完成了所有必要的配置。现在我们可以在我们的安全方法上设置必要的检查规则。
By default, Spring ACL refers to BasePermission class for all available permissions. Basically, we have a READ, WRITE, CREATE, DELETE and ADMINISTRATION permission.
默认情况下,Spring ACL引用BasePermission类来获得所有可用的权限。基本上,我们有一个读、写、创建、删除和管理权限。
Let’s try to define some security rules:
让我们试着定义一些安全规则。
@PostFilter("hasPermission(filterObject, 'READ')")
List<NoticeMessage> findAll();
@PostAuthorize("hasPermission(returnObject, 'READ')")
NoticeMessage findById(Integer id);
@PreAuthorize("hasPermission(#noticeMessage, 'WRITE')")
NoticeMessage save(@Param("noticeMessage")NoticeMessage noticeMessage);
After the execution of findAll() method, @PostFilter will be triggered. The required rule hasPermission(filterObject, ‘READ’), means returning only those NoticeMessage which current user has READ permission on.
在执行findAll()方法后,@PostFilter将被触发。所需的规则hasPermission(filterObject, ‘READ’),意味着只返回那些当前用户有READ权限的NoticeMessage。
Similarly, @PostAuthorize is triggered after the execution of findById() method, make sure only return the NoticeMessage object if the current user has READ permission on it. If not, the system will throw an AccessDeniedException.
同样,@PostAuthorize在执行findById()方法后被触发,确保只返回NoticeMessage对象,如果当前用户对它有READ权限。如果没有,系统将抛出一个AccessDeniedException。
On the other side, the system triggers the @PreAuthorize annotation before invoking the save() method. It will decide where the corresponding method is allowed to execute or not. If not, AccessDeniedException will be thrown.
在另一边,系统在调用save()方法之前会触发@PreAuthorize注解。它将决定相应的方法是否被允许执行。如果不允许,AccessDeniedException将被抛出。
4. In Action
4.在行动中
Now we’re going to test all those configurations using JUnit. We’ll use H2 database to keep configuration as simple as possible.
现在我们要用JUnit来测试所有这些配置。我们将使用H2数据库来保持配置尽可能的简单。
We’ll need to add:
我们需要添加。
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
4.1. The Scenario
4.1.情景
In this scenario, we’ll have two users (manager, hr) and a one user role (ROLE_EDITOR), so our acl_sid will be:
在这种情况下,我们将有两个用户(manager, hr)和一个用户角色(ROLE_EDITOR),所以我们的acl_sid将是。
INSERT INTO acl_sid (id, principal, sid) VALUES
(1, 1, 'manager'),
(2, 1, 'hr'),
(3, 0, 'ROLE_EDITOR');
Then, we need to declare NoticeMessage class in acl_class. And three instances of NoticeMessage class will be inserted in system_message.
然后,我们需要在acl_class中声明NoticeMessage类。三个NoticeMessage类的实例将被插入到system_message中。
Moreover, corresponding records for those 3 instances must be declared in acl_object_identity:
此外,这3个实例的相应记录必须在acl_object_identity中声明。
INSERT INTO acl_class (id, class) VALUES
(1, 'com.baeldung.acl.persistence.entity.NoticeMessage');
INSERT INTO system_message(id,content) VALUES
(1,'First Level Message'),
(2,'Second Level Message'),
(3,'Third Level Message');
INSERT INTO acl_object_identity
(id, object_id_class, object_id_identity,
parent_object, owner_sid, entries_inheriting)
VALUES
(1, 1, 1, NULL, 3, 0),
(2, 1, 2, NULL, 3, 0),
(3, 1, 3, NULL, 3, 0);
Initially, we grant READ and WRITE permissions on the first object (id =1) to the user manager. Meanwhile, any user with ROLE_EDITOR will have READ permission on all three objects but only possess WRITE permission on the third object (id=3). Besides, user hr will have only READ permission on the second object.
最初,我们将第一个对象(id =1)的阅读和写入权限授予用户manager。同时,任何拥有ROLE_EDITOR的用户将拥有所有三个对象的READ权限,但只拥有第三个对象的WRITE权限(id=3)。此外,用户hr在第二个对象上将只有READ权限。
Here, because we use default Spring ACL BasePermission class for permission checking, the mask value of the READ permission will be 1, and the mask value of WRITE permission will be 2. Our data in acl_entry will be:
在这里,因为我们使用默认的Spring ACL BasePermission类进行权限检查,READ权限的掩码值将是1,而WRITE权限的掩码值将是2。我们在acl_entry的数据将是。
INSERT INTO acl_entry
(id, acl_object_identity, ace_order,
sid, mask, granting, audit_success, audit_failure)
VALUES
(1, 1, 1, 1, 1, 1, 1, 1),
(2, 1, 2, 1, 2, 1, 1, 1),
(3, 1, 3, 3, 1, 1, 1, 1),
(4, 2, 1, 2, 1, 1, 1, 1),
(5, 2, 2, 3, 1, 1, 1, 1),
(6, 3, 1, 3, 1, 1, 1, 1),
(7, 3, 2, 3, 2, 1, 1, 1);
4.2. Test Case
4.2.测试案例
First of all, we try to call the findAll method.
首先,我们尝试调用findAll方法。
As our configuration, the method returns only those NoticeMessage on which the user has READ permission.
正如我们的配置,该方法只返回那些用户有阅读权限的NoticeMessage。
Hence, we expect the result list contains only the first message:
因此,我们希望结果列表中只包含第一条信息。
@Test
@WithMockUser(username = "manager")
public void
givenUserManager_whenFindAllMessage_thenReturnFirstMessage(){
List<NoticeMessage> details = repo.findAll();
assertNotNull(details);
assertEquals(1,details.size());
assertEquals(FIRST_MESSAGE_ID,details.get(0).getId());
}
Then we try to call the same method with any user which has the role – ROLE_EDITOR. Note that, in this case, these users have the READ permission on all three objects.
然后我们尝试用任何拥有ROLE_EDITOR角色的用户来调用同一个方法。注意,在这种情况下,这些用户对所有三个对象都有阅读权限。
Hence, we expect the result list will contain all three messages:
因此,我们预计结果列表将包含所有三个信息。
@Test
@WithMockUser(roles = {"EDITOR"})
public void
givenRoleEditor_whenFindAllMessage_thenReturn3Message(){
List<NoticeMessage> details = repo.findAll();
assertNotNull(details);
assertEquals(3,details.size());
}
Next, using the manager user, we’ll try to get the first message by id and update its content – which should all work fine:
接下来,使用manager用户,我们将尝试通过id获得第一条信息并更新其内容–这应该都能正常工作。
@Test
@WithMockUser(username = "manager")
public void
givenUserManager_whenFind1stMessageByIdAndUpdateItsContent_thenOK(){
NoticeMessage firstMessage = repo.findById(FIRST_MESSAGE_ID);
assertNotNull(firstMessage);
assertEquals(FIRST_MESSAGE_ID,firstMessage.getId());
firstMessage.setContent(EDITTED_CONTENT);
repo.save(firstMessage);
NoticeMessage editedFirstMessage = repo.findById(FIRST_MESSAGE_ID);
assertNotNull(editedFirstMessage);
assertEquals(FIRST_MESSAGE_ID,editedFirstMessage.getId());
assertEquals(EDITTED_CONTENT,editedFirstMessage.getContent());
}
But if any user with the ROLE_EDITOR role updates the content of the first message – our system will throw an AccessDeniedException:
但如果任何具有ROLE_EDITOR角色的用户更新第一条信息的内容–我们的系统将抛出一个AccessDeniedException。
@Test(expected = AccessDeniedException.class)
@WithMockUser(roles = {"EDITOR"})
public void
givenRoleEditor_whenFind1stMessageByIdAndUpdateContent_thenFail(){
NoticeMessage firstMessage = repo.findById(FIRST_MESSAGE_ID);
assertNotNull(firstMessage);
assertEquals(FIRST_MESSAGE_ID,firstMessage.getId());
firstMessage.setContent(EDITTED_CONTENT);
repo.save(firstMessage);
}
Similarly, the hr user can find the second message by id, but will fail to update it:
同样地,hr用户可以通过id找到第二条信息,但将无法更新它。
@Test
@WithMockUser(username = "hr")
public void givenUsernameHr_whenFindMessageById2_thenOK(){
NoticeMessage secondMessage = repo.findById(SECOND_MESSAGE_ID);
assertNotNull(secondMessage);
assertEquals(SECOND_MESSAGE_ID,secondMessage.getId());
}
@Test(expected = AccessDeniedException.class)
@WithMockUser(username = "hr")
public void givenUsernameHr_whenUpdateMessageWithId2_thenFail(){
NoticeMessage secondMessage = new NoticeMessage();
secondMessage.setId(SECOND_MESSAGE_ID);
secondMessage.setContent(EDITTED_CONTENT);
repo.save(secondMessage);
}
5. Conclusion
5.结论
We’ve gone through basic configuration and usage of Spring ACL in this article.
在这篇文章中,我们已经经历了Spring ACL的基本配置和使用。
As we know, Spring ACL required specific tables for managing object, principle/authority, and permission setting. All interactions with those tables, especially updating action, must go through AclService. We’ll explore this service for basic CRUD actions in a future article.
正如我们所知,Spring ACL需要特定的表来管理对象、原则/权限和权限设置。所有与这些表的交互,尤其是更新动作,必须通过AclService。我们将在未来的文章中探讨这个服务的基本CRUD动作。
By default, we are restricted to predefined permission in BasePermission class.
默认情况下,我们被限制在BasePermission类中预定义的权限。
Finally, the implementation of this tutorial can be found over on Github.
最后,本教程的实现可以在Github上找到over。