diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml
index b00af7fc94..dfecbc8e29 100644
--- a/spring-boot/pom.xml
+++ b/spring-boot/pom.xml
@@ -40,6 +40,11 @@
org.springframework.boot
spring-boot-starter-security
+
+
+ org.springframework.security
+ spring-security-test
+
org.springframework.boot
@@ -125,6 +130,18 @@
mysql-connector-java
6.0.6
+
+
+ org.togglz
+ togglz-spring-boot-starter
+ ${togglz.version}
+
+
+
+ org.togglz
+ togglz-spring-security
+ ${togglz.version}
+
@@ -262,6 +279,7 @@
3.1.7
8.5.11
1.4.194
+ 2.4.1.Final
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/Employee.java b/spring-boot/src/main/java/com/baeldung/toggle/Employee.java
new file mode 100644
index 0000000000..64a8b3ce5b
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/Employee.java
@@ -0,0 +1,37 @@
+package com.baeldung.toggle;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Entity
+public class Employee {
+
+ @Id
+ private long id;
+ private double salary;
+
+ public Employee() {
+ }
+
+ public Employee(long id, double salary) {
+ this.id = id;
+ this.salary = salary;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public double getSalary() {
+ return salary;
+ }
+
+ public void setSalary(double salary) {
+ this.salary = salary;
+ }
+
+}
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/EmployeeRepository.java b/spring-boot/src/main/java/com/baeldung/toggle/EmployeeRepository.java
new file mode 100644
index 0000000000..7ea7c11fde
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/EmployeeRepository.java
@@ -0,0 +1,7 @@
+package com.baeldung.toggle;
+
+import org.springframework.data.repository.CrudRepository;
+
+public interface EmployeeRepository extends CrudRepository{
+
+}
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/FeatureAssociation.java b/spring-boot/src/main/java/com/baeldung/toggle/FeatureAssociation.java
new file mode 100644
index 0000000000..4578b8498e
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/FeatureAssociation.java
@@ -0,0 +1,12 @@
+package com.baeldung.toggle;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface FeatureAssociation {
+ MyFeatures value();
+}
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/FeaturesAspect.java b/spring-boot/src/main/java/com/baeldung/toggle/FeaturesAspect.java
new file mode 100644
index 0000000000..9bc643fccc
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/FeaturesAspect.java
@@ -0,0 +1,27 @@
+package com.baeldung.toggle;
+
+import org.apache.log4j.Logger;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class FeaturesAspect {
+
+ private static final Logger LOG = Logger.getLogger(FeaturesAspect.class);
+
+ @Around(value = "@within(featureAssociation) || @annotation(featureAssociation)")
+ public Object checkAspect(ProceedingJoinPoint joinPoint, FeatureAssociation featureAssociation) throws Throwable {
+ if (featureAssociation.value()
+ .isActive()) {
+ return joinPoint.proceed();
+ } else {
+ LOG.info("Feature " + featureAssociation.value()
+ .name() + " is not enabled!");
+ return null;
+ }
+ }
+
+}
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/MyFeatures.java b/spring-boot/src/main/java/com/baeldung/toggle/MyFeatures.java
new file mode 100644
index 0000000000..e19d302c76
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/MyFeatures.java
@@ -0,0 +1,23 @@
+package com.baeldung.toggle;
+
+import org.togglz.core.Feature;
+import org.togglz.core.activation.UserRoleActivationStrategy;
+import org.togglz.core.annotation.ActivationParameter;
+import org.togglz.core.annotation.DefaultActivationStrategy;
+import org.togglz.core.annotation.EnabledByDefault;
+import org.togglz.core.annotation.Label;
+import org.togglz.core.context.FeatureContext;
+
+public enum MyFeatures implements Feature {
+
+ @Label("Administrator Feature")
+ @EnabledByDefault
+ @DefaultActivationStrategy(id = UserRoleActivationStrategy.ID, parameters = { @ActivationParameter(name = UserRoleActivationStrategy.PARAM_ROLES_NAME, value = "ROLE_ADMIN") })
+ ADMIN_FEATURE;
+
+ public boolean isActive() {
+ return FeatureContext.getFeatureManager()
+ .isActive(this);
+ }
+
+}
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/SalaryController.java b/spring-boot/src/main/java/com/baeldung/toggle/SalaryController.java
new file mode 100644
index 0000000000..5d72f0105a
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/SalaryController.java
@@ -0,0 +1,20 @@
+package com.baeldung.toggle;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+public class SalaryController {
+
+ @Autowired
+ SalaryService salaryService;
+
+ @PostMapping(value = "/increaseSalary")
+ @ResponseBody
+ public void increaseSalary(@RequestParam long id) {
+ salaryService.increaseSalary(id);
+ }
+}
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/SalaryService.java b/spring-boot/src/main/java/com/baeldung/toggle/SalaryService.java
new file mode 100644
index 0000000000..4ea4a2ab35
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/SalaryService.java
@@ -0,0 +1,19 @@
+package com.baeldung.toggle;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SalaryService {
+
+ @Autowired
+ EmployeeRepository employeeRepository;
+
+ @FeatureAssociation(value = MyFeatures.ADMIN_FEATURE)
+ public void increaseSalary(long id) {
+ Employee employee = employeeRepository.findOne(id);
+ employee.setSalary(employee.getSalary() + employee.getSalary() * 0.1);
+ employeeRepository.save(employee);
+ }
+
+}
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/SecurityConfig.java b/spring-boot/src/main/java/com/baeldung/toggle/SecurityConfig.java
new file mode 100644
index 0000000000..f558ccb0e1
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/SecurityConfig.java
@@ -0,0 +1,33 @@
+package com.baeldung.toggle;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ //@formatter:off
+ auth.inMemoryAuthentication()
+ .withUser("user").password("pass").roles("USER")
+ .and()
+ .withUser("admin").password("pass").roles("ADMIN");
+ //@formatter:on
+ }
+
+ @Override
+ public void configure(HttpSecurity http) throws Exception {
+ //@formatter:off
+ http.authorizeRequests().antMatchers("/increaseSalary").permitAll()
+ .and()
+ .csrf().disable()
+ .httpBasic();
+ //@formatter:on
+ }
+}
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/ToggleApplication.java b/spring-boot/src/main/java/com/baeldung/toggle/ToggleApplication.java
new file mode 100644
index 0000000000..c269262ab2
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/ToggleApplication.java
@@ -0,0 +1,17 @@
+package com.baeldung.toggle;
+
+import javax.annotation.security.RolesAllowed;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import com.baeldung.autoconfiguration.MySQLAutoconfiguration;
+
+@SpringBootApplication(exclude = MySQLAutoconfiguration.class)
+public class ToggleApplication {
+ @RolesAllowed("*")
+ public static void main(String[] args) {
+ System.setProperty("security.basic.enabled", "false");
+ SpringApplication.run(ToggleApplication.class, args);
+ }
+}
diff --git a/spring-boot/src/main/java/com/baeldung/toggle/ToggleConfiguration.java b/spring-boot/src/main/java/com/baeldung/toggle/ToggleConfiguration.java
new file mode 100644
index 0000000000..3ae6abcbc9
--- /dev/null
+++ b/spring-boot/src/main/java/com/baeldung/toggle/ToggleConfiguration.java
@@ -0,0 +1,28 @@
+package com.baeldung.toggle;
+
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.togglz.core.manager.EnumBasedFeatureProvider;
+import org.togglz.core.spi.FeatureProvider;
+import org.togglz.core.user.UserProvider;
+import org.togglz.spring.security.SpringSecurityUserProvider;
+
+@Configuration
+@EnableJpaRepositories("com.baeldung.toggle")
+@EntityScan("com.baeldung.toggle")
+public class ToggleConfiguration {
+
+ @Bean
+ public FeatureProvider featureProvider() {
+ return new EnumBasedFeatureProvider(MyFeatures.class);
+ }
+
+ @Bean
+ public UserProvider userProvider() {
+ return new SpringSecurityUserProvider("admin");
+ }
+}
diff --git a/spring-boot/src/test/java/com/baeldung/toggle/ToggleTest.java b/spring-boot/src/test/java/com/baeldung/toggle/ToggleTest.java
new file mode 100644
index 0000000000..fa51d19e3a
--- /dev/null
+++ b/spring-boot/src/test/java/com/baeldung/toggle/ToggleTest.java
@@ -0,0 +1,84 @@
+package com.baeldung.toggle;
+
+import static org.junit.Assert.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = ToggleApplication.class)
+@AutoConfigureMockMvc
+public class ToggleTest {
+
+ @Autowired
+ SalaryService salaryService;
+
+ @Autowired
+ EmployeeRepository employeeRepository;
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ @Autowired
+ private FilterChainProxy springSecurityFilterChain;
+
+ @Before
+ public void setup() {
+ this.mvc = MockMvcBuilders.webAppContextSetup(this.wac)
+ .addFilter(springSecurityFilterChain)
+ .build();
+ }
+
+ @Test
+ public void givenNoAuthentication_whenIncreaseSalary_thenNoIncrease() throws Exception {
+ Employee emp = new Employee(1, 2000);
+ employeeRepository.save(emp);
+ mvc.perform(post("/increaseSalary").param("id", emp.getId() + ""))
+ .andExpect(status().is(200));
+
+ emp = employeeRepository.findOne(1L);
+ assertEquals("salary incorrect", 2000, emp.getSalary(), 0.5);
+ }
+
+ @Test
+ public void givenAdminAuthentication_whenIncreaseSalary_thenIncrease() throws Exception {
+ Employee emp = new Employee(1, 2000);
+ employeeRepository.save(emp);
+ mvc.perform(post("/increaseSalary").param("id", emp.getId() + "")
+ .with(httpBasic("admin", "pass")))
+ .andExpect(status().is(200));
+
+ emp = employeeRepository.findOne(1L);
+ assertEquals("salary incorrect", 2200, emp.getSalary(), 0.5);
+ }
+
+ @Test
+ public void givenUserAuthentication_whenIncreaseSalary_thenNoIncrease() throws Exception {
+ Employee emp = new Employee(1, 2000);
+ employeeRepository.save(emp);
+ mvc.perform(post("/increaseSalary").param("id", emp.getId() + "")
+ .with(httpBasic("user", "pass")))
+ .andExpect(status().is(200));
+
+ emp = employeeRepository.findOne(1L);
+ assertEquals("salary incorrect", 2000, emp.getSalary(), 0.5);
+ }
+
+}