diff --git a/spring-security-mvc-boot/pom.xml b/spring-security-mvc-boot/pom.xml
index 3a8bc1f3aa..6566c0eea6 100644
--- a/spring-security-mvc-boot/pom.xml
+++ b/spring-security-mvc-boot/pom.xml
@@ -122,6 +122,25 @@
jstl-api
${jstl.version}
+
+
+ org.springframework.security
+ spring-security-acl
+
+
+ org.springframework.security
+ spring-security-config
+
+
+ org.springframework
+ spring-context-support
+
+
+ net.sf.ehcache
+ ehcache-core
+ 2.6.11
+ jar
+
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/acl/config/ACLContext.java b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/config/ACLContext.java
new file mode 100644
index 0000000000..63a4ea58ef
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/config/ACLContext.java
@@ -0,0 +1,80 @@
+package org.baeldung.acl.config;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.cache.ehcache.EhCacheFactoryBean;
+import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.acls.AclPermissionCacheOptimizer;
+import org.springframework.security.acls.AclPermissionEvaluator;
+import org.springframework.security.acls.domain.AclAuthorizationStrategy;
+import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl;
+import org.springframework.security.acls.domain.ConsoleAuditLogger;
+import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy;
+import org.springframework.security.acls.domain.EhCacheBasedAclCache;
+import org.springframework.security.acls.jdbc.BasicLookupStrategy;
+import org.springframework.security.acls.jdbc.JdbcMutableAclService;
+import org.springframework.security.acls.jdbc.LookupStrategy;
+import org.springframework.security.acls.model.PermissionGrantingStrategy;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+@Configuration
+@EnableAutoConfiguration
+public class ACLContext {
+
+ @Autowired
+ DataSource dataSource;
+
+ @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 PermissionGrantingStrategy permissionGrantingStrategy() {
+ return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
+ }
+
+ @Bean
+ public AclAuthorizationStrategy aclAuthorizationStrategy() {
+ return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMIN"));
+ }
+
+ @Bean
+ public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
+ DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+ AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService());
+ expressionHandler.setPermissionEvaluator(permissionEvaluator);
+ expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
+ return expressionHandler;
+ }
+
+ @Bean
+ public LookupStrategy lookupStrategy() {
+ return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
+ }
+
+ @Bean
+ public JdbcMutableAclService aclService() {
+ return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
+ }
+
+}
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/acl/config/AclMethodSecurityConfiguration.java b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/config/AclMethodSecurityConfiguration.java
new file mode 100644
index 0000000000..110c4a6d24
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/config/AclMethodSecurityConfiguration.java
@@ -0,0 +1,21 @@
+package org.baeldung.acl.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
+
+@Configuration
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class AclMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
+
+ @Autowired
+ MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler;
+
+ @Override
+ protected MethodSecurityExpressionHandler createExpressionHandler() {
+ return defaultMethodSecurityExpressionHandler;
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/acl/config/JPAPersistenceConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/config/JPAPersistenceConfig.java
new file mode 100644
index 0000000000..9b87efa92c
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/config/JPAPersistenceConfig.java
@@ -0,0 +1,16 @@
+package org.baeldung.acl.config;
+
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(basePackages = "org.baeldung.acl.persistence.dao")
+@PropertySource("classpath:org.baeldung.acl.datasource.properties")
+@EntityScan(basePackages={ "org.baeldung.acl.persistence.entity" })
+public class JPAPersistenceConfig {
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/acl/persistence/dao/NoticeMessageRepository.java b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/persistence/dao/NoticeMessageRepository.java
new file mode 100644
index 0000000000..8662c88d6c
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/persistence/dao/NoticeMessageRepository.java
@@ -0,0 +1,24 @@
+package org.baeldung.acl.persistence.dao;
+
+import java.util.List;
+
+import org.baeldung.acl.persistence.entity.NoticeMessage;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.repository.query.Param;
+import org.springframework.security.access.prepost.PostAuthorize;
+import org.springframework.security.access.prepost.PostFilter;
+import org.springframework.security.access.prepost.PreAuthorize;
+
+public interface NoticeMessageRepository extends JpaRepository{
+
+ @PostFilter("hasPermission(filterObject, 'READ')")
+ List findAll();
+
+ @PostAuthorize("hasPermission(returnObject, 'READ')")
+ NoticeMessage findById(Integer id);
+
+ @SuppressWarnings("unchecked")
+ @PreAuthorize("hasPermission(#noticeMessage, 'WRITE')")
+ NoticeMessage save(@Param("noticeMessage")NoticeMessage noticeMessage);
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/acl/persistence/entity/NoticeMessage.java b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/persistence/entity/NoticeMessage.java
new file mode 100644
index 0000000000..23f01a8edb
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/acl/persistence/entity/NoticeMessage.java
@@ -0,0 +1,29 @@
+package org.baeldung.acl.persistence.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name="system_message")
+public class NoticeMessage {
+
+ @Id
+ @Column
+ private Integer id;
+ @Column
+ private String content;
+ public Integer getId() {
+ return id;
+ }
+ public void setId(Integer id) {
+ this.id = id;
+ }
+ public String getContent() {
+ return content;
+ }
+ public void setContent(String content) {
+ this.content = content;
+ }
+}
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/acl-data.sql b/spring-security-mvc-boot/src/main/resources/acl-data.sql
new file mode 100644
index 0000000000..6c01eaacc2
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/acl-data.sql
@@ -0,0 +1,28 @@
+INSERT INTO system_message(id,content) VALUES (1,'First Level Message');
+INSERT INTO system_message(id,content) VALUES (2,'Second Level Message');
+INSERT INTO system_message(id,content) VALUES (3,'Third Level Message');
+
+INSERT INTO acl_class (id, class) VALUES
+(1, 'org.baeldung.acl.persistence.entity.NoticeMessage');
+
+INSERT INTO acl_sid (id, principal, sid) VALUES
+(1, 1, 'manager'),
+(2, 1, 'hr'),
+(3, 1, 'admin'),
+(4, 0, 'ROLE_EDITOR');
+
+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)
+;
+
+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, 4, 1, 1, 1, 1),
+(4, 2, 1, 2, 1, 1, 1, 1),
+(5, 2, 2, 4, 1, 1, 1, 1),
+(6, 3, 1, 4, 1, 1, 1, 1),
+(7, 3, 2, 4, 2, 1, 1, 1)
+;
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/acl-schema.sql b/spring-security-mvc-boot/src/main/resources/acl-schema.sql
new file mode 100644
index 0000000000..58e9394b2b
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/acl-schema.sql
@@ -0,0 +1,58 @@
+create table system_message (id integer not null, content varchar(255), primary key (id));
+
+CREATE TABLE IF NOT EXISTS acl_sid (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ principal tinyint(1) NOT NULL,
+ sid varchar(100) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY unique_uk_1 (sid,principal)
+);
+
+CREATE TABLE IF NOT EXISTS acl_class (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ class varchar(255) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY unique_uk_2 (class)
+);
+
+CREATE TABLE IF NOT EXISTS acl_entry (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ acl_object_identity bigint(20) NOT NULL,
+ ace_order int(11) NOT NULL,
+ sid bigint(20) NOT NULL,
+ mask int(11) NOT NULL,
+ granting tinyint(1) NOT NULL,
+ audit_success tinyint(1) NOT NULL,
+ audit_failure tinyint(1) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY unique_uk_4 (acl_object_identity,ace_order)
+);
+
+CREATE TABLE IF NOT EXISTS acl_object_identity (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ object_id_class bigint(20) NOT NULL,
+ object_id_identity bigint(20) NOT NULL,
+ parent_object bigint(20) DEFAULT NULL,
+ owner_sid bigint(20) DEFAULT NULL,
+ entries_inheriting tinyint(1) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY unique_uk_3 (object_id_class,object_id_identity)
+);
+
+ALTER TABLE acl_entry
+ADD FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id);
+
+ALTER TABLE acl_entry
+ADD FOREIGN KEY (sid) REFERENCES acl_sid(id);
+
+--
+-- Constraints for table acl_object_identity
+--
+ALTER TABLE acl_object_identity
+ADD FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id);
+
+ALTER TABLE acl_object_identity
+ADD FOREIGN KEY (object_id_class) REFERENCES acl_class (id);
+
+ALTER TABLE acl_object_identity
+ADD FOREIGN KEY (owner_sid) REFERENCES acl_sid (id);
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/org.baeldung.acl.datasource.properties b/spring-security-mvc-boot/src/main/resources/org.baeldung.acl.datasource.properties
new file mode 100644
index 0000000000..739dd3f07c
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/org.baeldung.acl.datasource.properties
@@ -0,0 +1,12 @@
+spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE
+spring.datasource.username=sa
+spring.datasource.password=
+spring.datasource.driverClassName=org.h2.Driver
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
+
+spring.h2.console.path=/myconsole
+spring.h2.console.enabled=true
+spring.datasource.initialize=true
+spring.datasource.schema=classpath:acl-schema.sql
+spring.datasource.data=classpath:acl-data.sql
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/test/java/org/baeldung/acl/SpringAclTest.java b/spring-security-mvc-boot/src/test/java/org/baeldung/acl/SpringAclTest.java
new file mode 100644
index 0000000000..fd9069d9bc
--- /dev/null
+++ b/spring-security-mvc-boot/src/test/java/org/baeldung/acl/SpringAclTest.java
@@ -0,0 +1,119 @@
+package org.baeldung.acl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.List;
+
+import org.baeldung.acl.persistence.dao.NoticeMessageRepository;
+import org.baeldung.acl.persistence.entity.NoticeMessage;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
+import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
+import org.springframework.test.context.web.ServletTestExecutionListener;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@TestExecutionListeners(listeners={ServletTestExecutionListener.class,
+ DependencyInjectionTestExecutionListener.class,
+ DirtiesContextTestExecutionListener.class,
+ TransactionalTestExecutionListener.class,
+ WithSecurityContextTestExecutionListener.class})
+public class SpringAclTest extends AbstractJUnit4SpringContextTests{
+
+ private static Integer FIRST_MESSAGE_ID = 1;
+ private static Integer SECOND_MESSAGE_ID = 2;
+ private static Integer THIRD_MESSAGE_ID = 3;
+ private static String EDITTED_CONTENT = "EDITED";
+
+ @Configuration
+ @ComponentScan("org.baeldung.acl.*")
+ public static class SpringConfig {
+
+ }
+
+ @Autowired
+ NoticeMessageRepository repo;
+
+ @Test
+ @WithMockUser(username="manager")
+ public void givenUsernameManager_whenFindAllMessage_thenReturnFirstMessage(){
+ List details = repo.findAll();
+ assertNotNull(details);
+ assertEquals(1,details.size());
+ assertEquals(FIRST_MESSAGE_ID,details.get(0).getId());
+ }
+
+ @Test
+ @WithMockUser(username="manager")
+ public void givenUsernameManager_whenFindFirstMessageByIdAndUpdateFirstMessageContent_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());
+ }
+
+ @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);
+ }
+
+ @Test
+ @WithMockUser(roles={"EDITOR"})
+ public void givenRoleEditor_whenFindAllMessage_thenReturnThreeMessage(){
+ List details = repo.findAll();
+ assertNotNull(details);
+ assertEquals(3,details.size());
+ }
+
+ @Test
+ @WithMockUser(roles={"EDITOR"})
+ public void givenRoleEditor_whenUpdateThirdMessage_thenOK(){
+ NoticeMessage thirdMessage = new NoticeMessage();
+ thirdMessage.setId(THIRD_MESSAGE_ID);
+ thirdMessage.setContent(EDITTED_CONTENT);
+ repo.save(thirdMessage);
+ }
+
+ @Test(expected=AccessDeniedException.class)
+ @WithMockUser(roles={"EDITOR"})
+ public void givenRoleEditor_whenFindFirstMessageByIdAndUpdateFirstMessageContent_thenFail(){
+ NoticeMessage firstMessage = repo.findById(FIRST_MESSAGE_ID);
+ assertNotNull(firstMessage);
+ assertEquals(FIRST_MESSAGE_ID,firstMessage.getId());
+ firstMessage.setContent(EDITTED_CONTENT);
+ repo.save(firstMessage);
+ }
+}
+
\ No newline at end of file