diff --git a/spring-security-mvc-boot/README.MD b/spring-security-mvc-boot/README.MD
new file mode 100644
index 0000000000..d59aea97b4
--- /dev/null
+++ b/spring-security-mvc-boot/README.MD
@@ -0,0 +1,7 @@
+###The Course
+The "REST With Spring" Classes: http://github.learnspringsecurity.com
+
+###Relevant Articles:
+- [A Custom Security Expression with Spring Security](http://www.baeldung.com/spring-security-create-new-custom-security-expression)
+- [Custom AccessDecisionVoters in Spring Security](http://www.baeldung.com/spring-security-custom-voter)
+- [Spring Security: Authentication with a Database-backed UserDetailsService](http://www.baeldung.com/spring-security-authentication-with-a-database)
diff --git a/spring-security-mvc-boot/WebContent/META-INF/MANIFEST.MF b/spring-security-mvc-boot/WebContent/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..254272e1c0
--- /dev/null
+++ b/spring-security-mvc-boot/WebContent/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path:
+
diff --git a/spring-security-mvc-boot/pom.xml b/spring-security-mvc-boot/pom.xml
new file mode 100644
index 0000000000..b8563880b3
--- /dev/null
+++ b/spring-security-mvc-boot/pom.xml
@@ -0,0 +1,303 @@
+
+
+ 4.0.0
+
+ com.baeldung
+ spring-security-mvc-boot
+ 0.0.1-SNAPSHOT
+ war
+
+ spring-security-mvc-boot
+ Spring Security MVC Boot
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.4.2.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+
+ org.thymeleaf.extras
+ thymeleaf-extras-springsecurity4
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ com.h2database
+ h2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ junit
+ junit
+ test
+
+
+
+ org.hamcrest
+ hamcrest-core
+ test
+
+
+
+ org.hamcrest
+ hamcrest-library
+ test
+
+
+
+ com.jayway.restassured
+ rest-assured
+ ${rest-assured.version}
+ test
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.springframework
+ spring-test
+ test
+
+
+
+ org.apache.derby
+ derby
+ ${derby.version}
+
+
+ org.apache.derby
+ derbyclient
+ ${derby.version}
+
+
+ org.apache.derby
+ derbynet
+ ${derby.version}
+
+
+ org.apache.derby
+ derbytools
+ ${derby.version}
+
+
+ taglibs
+ standard
+ ${taglibs-standard.version}
+
+
+ org.springframework.security
+ spring-security-taglibs
+ ${spring-security-taglibs.version}
+
+
+
+ org.springframework.security
+ spring-security-core
+ ${spring-security-core.version}
+
+
+
+ javax.servlet.jsp.jstl
+ jstl-api
+ ${jstl.version}
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/*IntegrationTest.java
+ **/*LiveTest.java
+
+
+
+
+
+
+
+
+ org.codehaus.cargo
+ cargo-maven2-plugin
+ ${cargo-maven2-plugin.version}
+
+ true
+
+ tomcat8x
+ embedded
+
+
+
+
+
+
+ 8082
+
+
+
+
+
+
+
+
+
+
+
+ integration
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ integration-test
+
+ test
+
+
+
+ **/*LiveTest.java
+
+
+ **/*IntegrationTest.java
+
+
+
+
+
+
+ json
+
+
+
+
+
+
+
+
+
+ live
+
+
+
+ org.codehaus.cargo
+ cargo-maven2-plugin
+
+
+ start-server
+ pre-integration-test
+
+ start
+
+
+
+ stop-server
+ post-integration-test
+
+ stop
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ integration-test
+
+ test
+
+
+
+ none
+
+
+ **/*LiveTest.java
+
+
+ cargo
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.baeldung.Application
+
+
+
+
+ UTF-8
+ 1.8
+ 10.13.1.1
+ 1.1.2
+ 4.2.0.RELEASE
+ 4.2.0.RELEASE
+
+ 1.2
+ 2.4.0
+ 1.6.1
+
+
+
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/Application.java b/spring-security-mvc-boot/src/main/java/org/baeldung/Application.java
new file mode 100644
index 0000000000..072a6ca4fe
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/Application.java
@@ -0,0 +1,17 @@
+package org.baeldung;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.context.web.SpringBootServletInitializer;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+
+@Configuration
+@EnableAutoConfiguration
+@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.baeldung.voter.*"), @ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.baeldung.multiplelogin.*")})
+public class Application extends SpringBootServletInitializer {
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/config/MethodSecurityConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/config/MethodSecurityConfig.java
new file mode 100644
index 0000000000..c4624e85e0
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/config/MethodSecurityConfig.java
@@ -0,0 +1,21 @@
+package org.baeldung.config;
+
+import org.baeldung.security.CustomMethodSecurityExpressionHandler;
+import org.baeldung.security.CustomPermissionEvaluator;
+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)
+public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
+
+ @Override
+ protected MethodSecurityExpressionHandler createExpressionHandler() {
+ // final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+ final CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler();
+ expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
+ return expressionHandler;
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/config/MvcConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/config/MvcConfig.java
new file mode 100644
index 0000000000..9ade60e54c
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/config/MvcConfig.java
@@ -0,0 +1,42 @@
+package org.baeldung.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+@Configuration
+@EnableWebMvc
+public class MvcConfig extends WebMvcConfigurerAdapter {
+
+ public MvcConfig() {
+ super();
+ }
+
+ //
+ @Bean
+ public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
+ return new PropertySourcesPlaceholderConfigurer();
+ }
+
+ @Override
+ public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) {
+ configurer.enable();
+ }
+
+ @Override
+ public void addViewControllers(final ViewControllerRegistry registry) {
+ super.addViewControllers(registry);
+ registry.addViewController("/").setViewName("forward:/index");
+ registry.addViewController("/index");
+ }
+
+ @Override
+ public void addResourceHandlers(final ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
+ }
+}
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/config/PersistenceConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/config/PersistenceConfig.java
new file mode 100644
index 0000000000..1c4cb0124a
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/config/PersistenceConfig.java
@@ -0,0 +1,82 @@
+package org.baeldung.config;
+
+import java.util.Properties;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.env.Environment;
+import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@Configuration
+@EnableTransactionManagement
+@PropertySource({ "classpath:persistence-derby.properties" })
+@EnableJpaRepositories(basePackages = "org.baeldung.persistence.dao")
+public class PersistenceConfig {
+
+ @Autowired
+ private Environment env;
+
+ public PersistenceConfig() {
+ super();
+ }
+
+ // beans
+
+ @Bean
+ public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
+ final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
+ em.setDataSource(dataSource());
+ em.setPackagesToScan(new String[] { "org.baeldung.persistence.model" });
+
+ final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+ em.setJpaVendorAdapter(vendorAdapter);
+ em.setJpaProperties(additionalProperties());
+
+ return em;
+ }
+
+ @Bean
+ public DataSource dataSource() {
+ final DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
+ dataSource.setUrl(env.getProperty("jdbc.url"));
+ dataSource.setUsername(env.getProperty("jdbc.user"));
+ dataSource.setPassword(env.getProperty("jdbc.pass"));
+
+ return dataSource;
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
+ final JpaTransactionManager transactionManager = new JpaTransactionManager();
+ transactionManager.setEntityManagerFactory(emf);
+ return transactionManager;
+ }
+
+ @Bean
+ public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
+ return new PersistenceExceptionTranslationPostProcessor();
+ }
+
+ final Properties additionalProperties() {
+ final Properties hibernateProperties = new Properties();
+ hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
+ hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
+ hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", env.getProperty("hibernate.cache.use_second_level_cache"));
+ hibernateProperties.setProperty("hibernate.cache.use_query_cache", env.getProperty("hibernate.cache.use_query_cache"));
+ // hibernateProperties.setProperty("hibernate.globally_quoted_identifiers", "true");
+ return hibernateProperties;
+ }
+}
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/config/SecurityConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/config/SecurityConfig.java
new file mode 100644
index 0000000000..8cc9d45823
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/config/SecurityConfig.java
@@ -0,0 +1,60 @@
+package org.baeldung.config;
+
+import org.baeldung.security.MyUserDetailsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+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.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+@EnableWebSecurity
+@ComponentScan("org.baeldung.security")
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ private MyUserDetailsService userDetailsService;
+
+ @Override
+ protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
+ auth.userDetailsService(userDetailsService);
+ }
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web.ignoring().antMatchers("/resources/**");
+ }
+
+ @Override
+ protected void configure(final HttpSecurity http) throws Exception {
+ // @formatter:off
+ http.authorizeRequests()
+ .antMatchers("/login").permitAll()
+ .antMatchers("/admin").hasRole("ADMIN")
+ .anyRequest().authenticated()
+ .and().formLogin().permitAll()
+ .and().csrf().disable();
+ ;
+ // @formatter:on
+ }
+
+ @Bean
+ public DaoAuthenticationProvider authenticationProvider() {
+ final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(userDetailsService);
+ authProvider.setPasswordEncoder(encoder());
+ return authProvider;
+ }
+
+ @Bean
+ public PasswordEncoder encoder() {
+ return new BCryptPasswordEncoder(11);
+ }
+}
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/MultipleLoginApplication.java b/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/MultipleLoginApplication.java
new file mode 100644
index 0000000000..23fda0547f
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/MultipleLoginApplication.java
@@ -0,0 +1,22 @@
+package org.baeldung.multiplelogin;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.context.web.SpringBootServletInitializer;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+@SpringBootApplication
+@ComponentScan("org.baeldung.multiplelogin")
+public class MultipleLoginApplication extends SpringBootServletInitializer {
+ public static void main(String[] args) {
+ SpringApplication.run(MultipleLoginApplication.class, args);
+ }
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ return application.sources(MultipleLoginApplication.class);
+ }
+}
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/MultipleLoginMvcConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/MultipleLoginMvcConfig.java
new file mode 100644
index 0000000000..ec76be1909
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/MultipleLoginMvcConfig.java
@@ -0,0 +1,45 @@
+package org.baeldung.multiplelogin;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.ViewResolver;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+import org.springframework.web.servlet.view.InternalResourceViewResolver;
+import org.springframework.web.servlet.view.JstlView;
+import org.springframework.context.annotation.ComponentScan;
+
+@EnableWebMvc
+@Configuration
+@ComponentScan("org.baeldung.controller")
+public class MultipleLoginMvcConfig extends WebMvcConfigurerAdapter {
+
+ public MultipleLoginMvcConfig() {
+ super();
+ }
+
+ // API
+
+ @Override
+ public void addViewControllers(final ViewControllerRegistry registry) {
+ super.addViewControllers(registry);
+
+ registry.addViewController("/anonymous.html");
+
+ registry.addViewController("/login.html");
+ registry.addViewController("/homepage.html");
+ registry.addViewController("/console.html");
+ }
+
+ @Bean
+ public ViewResolver viewResolver() {
+ final InternalResourceViewResolver bean = new InternalResourceViewResolver();
+
+ bean.setViewClass(JstlView.class);
+ bean.setPrefix("/WEB-INF/view/");
+ bean.setSuffix(".jsp");
+
+ return bean;
+ }
+}
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/MultipleLoginSecurityConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/MultipleLoginSecurityConfig.java
new file mode 100644
index 0000000000..d7dbf17723
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/MultipleLoginSecurityConfig.java
@@ -0,0 +1,122 @@
+package org.baeldung.multiplelogin;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.TestingAuthenticationProvider;
+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;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+
+@Configuration
+@EnableWebSecurity
+public class MultipleLoginSecurityConfig {
+
+ @Bean
+ public UserDetailsService userDetailsService() throws Exception {
+ InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
+ manager.createUser(User.withUsername("user")
+ .password("userPass")
+ .roles("USER")
+ .build());
+ manager.createUser(User.withUsername("admin")
+ .password("adminPass")
+ .roles("ADMIN")
+ .build());
+ return manager;
+ }
+
+ @Configuration
+ @Order(1)
+ public static class App1ConfigurationAdapter extends WebSecurityConfigurerAdapter {
+
+ public App1ConfigurationAdapter() {
+ super();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication()
+ .withUser("admin")
+ .password("admin")
+ .roles("ADMIN");
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.antMatcher("/admin*")
+ .authorizeRequests()
+ .anyRequest()
+ .hasRole("ADMIN")
+ // log in
+ .and()
+ .formLogin()
+ .loginPage("/loginAdmin")
+ .loginProcessingUrl("/admin_login")
+ .failureUrl("/loginAdmin?error=loginError")
+ .defaultSuccessUrl("/adminPage")
+ // logout
+ .and()
+ .logout()
+ .logoutUrl("/admin_logout")
+ .logoutSuccessUrl("/protectedLinks")
+ .deleteCookies("JSESSIONID")
+ .and()
+ .exceptionHandling()
+ .accessDeniedPage("/403")
+ .and()
+ .csrf()
+ .disable();
+ }
+ }
+
+ @Configuration
+ @Order(2)
+ public static class App2ConfigurationAdapter extends WebSecurityConfigurerAdapter {
+
+ public App2ConfigurationAdapter() {
+ super();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication()
+ .withUser("user")
+ .password("user")
+ .roles("USER");
+ }
+
+ protected void configure(HttpSecurity http) throws Exception {
+ http.antMatcher("/user*")
+ .authorizeRequests()
+ .anyRequest()
+ .hasRole("USER")
+ // log in
+ .and()
+ .formLogin()
+ .loginPage("/loginUser")
+ .loginProcessingUrl("/user_login")
+ .failureUrl("/loginUser?error=loginError")
+ .defaultSuccessUrl("/userPage")
+ // logout
+ .and()
+ .logout()
+ .logoutUrl("/user_logout")
+ .logoutSuccessUrl("/protectedLinks")
+ .deleteCookies("JSESSIONID")
+ .and()
+ .exceptionHandling()
+ .accessDeniedPage("/403")
+ .and()
+ .csrf()
+ .disable();
+ }
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/UsersController.java b/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/UsersController.java
new file mode 100644
index 0000000000..0672a760af
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/multiplelogin/UsersController.java
@@ -0,0 +1,38 @@
+package org.baeldung.multiplelogin;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class UsersController {
+
+ @RequestMapping("/protectedLinks")
+ public String getAnonymousPage() {
+ return "protectedLinks";
+ }
+
+ @RequestMapping("/userPage")
+ public String getUserPage() {
+ return "userPage";
+ }
+
+ @RequestMapping("/adminPage")
+ public String getAdminPage() {
+ return "adminPage";
+ }
+
+ @RequestMapping("/loginAdmin")
+ public String getAdminLoginPage() {
+ return "loginAdmin";
+ }
+
+ @RequestMapping("/loginUser")
+ public String getUserLoginPage() {
+ return "loginUser";
+ }
+
+ @RequestMapping("/403")
+ public String getAccessDeniedPage() {
+ return "403";
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/SetupData.java b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/SetupData.java
new file mode 100644
index 0000000000..631c8dfc58
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/SetupData.java
@@ -0,0 +1,69 @@
+package org.baeldung.persistence;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import javax.annotation.PostConstruct;
+
+import org.baeldung.persistence.dao.OrganizationRepository;
+import org.baeldung.persistence.dao.PrivilegeRepository;
+import org.baeldung.persistence.dao.UserRepository;
+import org.baeldung.persistence.model.Organization;
+import org.baeldung.persistence.model.Privilege;
+import org.baeldung.persistence.model.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SetupData {
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private PrivilegeRepository privilegeRepository;
+
+ @Autowired
+ private OrganizationRepository organizationRepository;
+
+ @PostConstruct
+ public void init() {
+ initOrganizations();
+ initPrivileges();
+ initUsers();
+ }
+
+ private void initUsers() {
+ final Privilege privilege1 = privilegeRepository.findByName("FOO_READ_PRIVILEGE");
+ final Privilege privilege2 = privilegeRepository.findByName("FOO_WRITE_PRIVILEGE");
+ //
+ final User user1 = new User();
+ user1.setUsername("john");
+ user1.setPassword("123");
+ user1.setPrivileges(new HashSet(Arrays.asList(privilege1)));
+ user1.setOrganization(organizationRepository.findByName("FirstOrg"));
+ userRepository.save(user1);
+ //
+ final User user2 = new User();
+ user2.setUsername("tom");
+ user2.setPassword("111");
+ user2.setPrivileges(new HashSet(Arrays.asList(privilege1, privilege2)));
+ user2.setOrganization(organizationRepository.findByName("SecondOrg"));
+ userRepository.save(user2);
+ }
+
+ private void initOrganizations() {
+ final Organization org1 = new Organization("FirstOrg");
+ organizationRepository.save(org1);
+ //
+ final Organization org2 = new Organization("SecondOrg");
+ organizationRepository.save(org2);
+ }
+
+ private void initPrivileges() {
+ final Privilege privilege1 = new Privilege("FOO_READ_PRIVILEGE");
+ privilegeRepository.save(privilege1);
+ //
+ final Privilege privilege2 = new Privilege("FOO_WRITE_PRIVILEGE");
+ privilegeRepository.save(privilege2);
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/dao/OrganizationRepository.java b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/dao/OrganizationRepository.java
new file mode 100644
index 0000000000..a20d24057b
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/dao/OrganizationRepository.java
@@ -0,0 +1,10 @@
+package org.baeldung.persistence.dao;
+
+import org.baeldung.persistence.model.Organization;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface OrganizationRepository extends JpaRepository {
+
+ public Organization findByName(String name);
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/dao/PrivilegeRepository.java b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/dao/PrivilegeRepository.java
new file mode 100644
index 0000000000..edf9002c3d
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/dao/PrivilegeRepository.java
@@ -0,0 +1,10 @@
+package org.baeldung.persistence.dao;
+
+import org.baeldung.persistence.model.Privilege;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PrivilegeRepository extends JpaRepository {
+
+ public Privilege findByName(String name);
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/dao/UserRepository.java b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/dao/UserRepository.java
new file mode 100644
index 0000000000..337106a187
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/dao/UserRepository.java
@@ -0,0 +1,14 @@
+package org.baeldung.persistence.dao;
+
+import org.baeldung.persistence.model.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.transaction.annotation.Transactional;
+
+public interface UserRepository extends JpaRepository {
+
+ User findByUsername(final String username);
+
+ @Transactional
+ void removeUserByUsername(String username);
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/Foo.java b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/Foo.java
new file mode 100644
index 0000000000..29c19cf22e
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/Foo.java
@@ -0,0 +1,94 @@
+package org.baeldung.persistence.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Foo {
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Column(nullable = false)
+ private String name;
+
+ //
+
+ public Foo() {
+ super();
+ }
+
+ public Foo(String name) {
+ super();
+ this.name = name;
+ }
+
+ //
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ //
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Foo [id=").append(id).append(", name=").append(name).append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((id == null) ? 0 : id.hashCode());
+ result = (prime * result) + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Foo other = (Foo) obj;
+ if (id == null) {
+ if (other.id != null) {
+ return false;
+ }
+ } else if (!id.equals(other.id)) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/Organization.java b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/Organization.java
new file mode 100644
index 0000000000..645285b5e9
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/Organization.java
@@ -0,0 +1,95 @@
+package org.baeldung.persistence.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Organization {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Column(nullable = false, unique = true)
+ private String name;
+
+ //
+
+ public Organization() {
+ super();
+ }
+
+ public Organization(String name) {
+ super();
+ this.name = name;
+ }
+
+ //
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ //
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Organization [id=").append(id).append(", name=").append(name).append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((id == null) ? 0 : id.hashCode());
+ result = (prime * result) + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Organization other = (Organization) obj;
+ if (id == null) {
+ if (other.id != null) {
+ return false;
+ }
+ } else if (!id.equals(other.id)) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/Privilege.java b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/Privilege.java
new file mode 100644
index 0000000000..ff3ae62c25
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/Privilege.java
@@ -0,0 +1,95 @@
+package org.baeldung.persistence.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+public class Privilege {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Column(nullable = false, unique = true)
+ private String name;
+
+ //
+
+ public Privilege() {
+ super();
+ }
+
+ public Privilege(String name) {
+ super();
+ this.name = name;
+ }
+
+ //
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ //
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Privilege [id=").append(id).append(", name=").append(name).append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((id == null) ? 0 : id.hashCode());
+ result = (prime * result) + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Privilege other = (Privilege) obj;
+ if (id == null) {
+ if (other.id != null) {
+ return false;
+ }
+ } else if (!id.equals(other.id)) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/User.java b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/User.java
new file mode 100644
index 0000000000..2ff8beebf0
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/persistence/model/User.java
@@ -0,0 +1,155 @@
+package org.baeldung.persistence.model;
+
+import java.util.Set;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "user_table")
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Column(nullable = false, unique = true)
+ private String username;
+
+ private String password;
+
+ @ManyToMany(fetch = FetchType.EAGER)
+ @JoinTable(name = "users_privileges", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "privilege_id", referencedColumnName = "id"))
+ private Set privileges;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "organization_id", referencedColumnName = "id")
+ private Organization organization;
+
+ //
+
+ public User() {
+ super();
+ }
+
+ //
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Set getPrivileges() {
+ return privileges;
+ }
+
+ public void setPrivileges(Set privileges) {
+ this.privileges = privileges;
+ }
+
+ public Organization getOrganization() {
+ return organization;
+ }
+
+ public void setOrganization(Organization organization) {
+ this.organization = organization;
+ }
+
+ //
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("User [id=").append(id).append(", username=").append(username).append(", password=").append(password).append(", privileges=").append(privileges).append(", organization=").append(organization).append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((id == null) ? 0 : id.hashCode());
+ result = (prime * result) + ((organization == null) ? 0 : organization.hashCode());
+ result = (prime * result) + ((password == null) ? 0 : password.hashCode());
+ result = (prime * result) + ((privileges == null) ? 0 : privileges.hashCode());
+ result = (prime * result) + ((username == null) ? 0 : username.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final User other = (User) obj;
+ if (id == null) {
+ if (other.id != null) {
+ return false;
+ }
+ } else if (!id.equals(other.id)) {
+ return false;
+ }
+ if (organization == null) {
+ if (other.organization != null) {
+ return false;
+ }
+ } else if (!organization.equals(other.organization)) {
+ return false;
+ }
+ if (password == null) {
+ if (other.password != null) {
+ return false;
+ }
+ } else if (!password.equals(other.password)) {
+ return false;
+ }
+ if (privileges == null) {
+ if (other.privileges != null) {
+ return false;
+ }
+ } else if (!privileges.equals(other.privileges)) {
+ return false;
+ }
+ if (username == null) {
+ if (other.username != null) {
+ return false;
+ }
+ } else if (!username.equals(other.username)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionHandler.java b/spring-security-mvc-boot/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionHandler.java
new file mode 100644
index 0000000000..e040a0b109
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionHandler.java
@@ -0,0 +1,22 @@
+package org.baeldung.security;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.core.Authentication;
+
+public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
+ private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+
+ @Override
+ protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
+ // final CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
+ final MySecurityExpressionRoot root = new MySecurityExpressionRoot(authentication);
+ root.setPermissionEvaluator(getPermissionEvaluator());
+ root.setTrustResolver(this.trustResolver);
+ root.setRoleHierarchy(getRoleHierarchy());
+ return root;
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionRoot.java b/spring-security-mvc-boot/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionRoot.java
new file mode 100644
index 0000000000..2d84536a14
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionRoot.java
@@ -0,0 +1,50 @@
+package org.baeldung.security;
+
+import org.baeldung.persistence.model.User;
+import org.springframework.security.access.expression.SecurityExpressionRoot;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
+import org.springframework.security.core.Authentication;
+
+public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
+
+ private Object filterObject;
+ private Object returnObject;
+
+ public CustomMethodSecurityExpressionRoot(Authentication authentication) {
+ super(authentication);
+ }
+
+ //
+ public boolean isMember(Long OrganizationId) {
+ final User user = ((MyUserPrincipal) this.getPrincipal()).getUser();
+ return user.getOrganization().getId().longValue() == OrganizationId.longValue();
+ }
+
+ //
+
+ @Override
+ public Object getFilterObject() {
+ return this.filterObject;
+ }
+
+ @Override
+ public Object getReturnObject() {
+ return this.returnObject;
+ }
+
+ @Override
+ public Object getThis() {
+ return this;
+ }
+
+ @Override
+ public void setFilterObject(Object obj) {
+ this.filterObject = obj;
+ }
+
+ @Override
+ public void setReturnObject(Object obj) {
+ this.returnObject = obj;
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/security/CustomPermissionEvaluator.java b/spring-security-mvc-boot/src/main/java/org/baeldung/security/CustomPermissionEvaluator.java
new file mode 100644
index 0000000000..5d96673a8f
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/security/CustomPermissionEvaluator.java
@@ -0,0 +1,40 @@
+package org.baeldung.security;
+
+import java.io.Serializable;
+
+import org.springframework.security.access.PermissionEvaluator;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+
+public class CustomPermissionEvaluator implements PermissionEvaluator {
+
+ @Override
+ public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {
+ if ((auth == null) || (targetDomainObject == null) || !(permission instanceof String)) {
+ return false;
+ }
+ final String targetType = targetDomainObject.getClass().getSimpleName().toUpperCase();
+ return hasPrivilege(auth, targetType, permission.toString().toUpperCase());
+ }
+
+ @Override
+ public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) {
+ if ((auth == null) || (targetType == null) || !(permission instanceof String)) {
+ return false;
+ }
+ return hasPrivilege(auth, targetType.toUpperCase(), permission.toString().toUpperCase());
+ }
+
+ private boolean hasPrivilege(Authentication auth, String targetType, String permission) {
+ for (final GrantedAuthority grantedAuth : auth.getAuthorities()) {
+ System.out.println("here " + grantedAuth);
+ if (grantedAuth.getAuthority().startsWith(targetType)) {
+ if (grantedAuth.getAuthority().contains(permission)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/security/MySecurityExpressionRoot.java b/spring-security-mvc-boot/src/main/java/org/baeldung/security/MySecurityExpressionRoot.java
new file mode 100644
index 0000000000..4d3561b325
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/security/MySecurityExpressionRoot.java
@@ -0,0 +1,203 @@
+package org.baeldung.security;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.baeldung.persistence.model.User;
+import org.springframework.security.access.PermissionEvaluator;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+
+public class MySecurityExpressionRoot implements MethodSecurityExpressionOperations {
+ protected final Authentication authentication;
+ private AuthenticationTrustResolver trustResolver;
+ private RoleHierarchy roleHierarchy;
+ private Set roles;
+ private String defaultRolePrefix = "ROLE_";
+
+ public final boolean permitAll = true;
+ public final boolean denyAll = false;
+ private PermissionEvaluator permissionEvaluator;
+ public final String read = "read";
+ public final String write = "write";
+ public final String create = "create";
+ public final String delete = "delete";
+ public final String admin = "administration";
+
+ //
+
+ private Object filterObject;
+ private Object returnObject;
+
+ public MySecurityExpressionRoot(Authentication authentication) {
+ if (authentication == null) {
+ throw new IllegalArgumentException("Authentication object cannot be null");
+ }
+ this.authentication = authentication;
+ }
+
+ @Override
+ public final boolean hasAuthority(String authority) {
+ throw new RuntimeException("method hasAuthority() not allowed");
+ }
+
+ //
+ public boolean isMember(Long OrganizationId) {
+ final User user = ((MyUserPrincipal) this.getPrincipal()).getUser();
+ return user.getOrganization().getId().longValue() == OrganizationId.longValue();
+ }
+
+ //
+
+ @Override
+ public final boolean hasAnyAuthority(String... authorities) {
+ return hasAnyAuthorityName(null, authorities);
+ }
+
+ @Override
+ public final boolean hasRole(String role) {
+ return hasAnyRole(role);
+ }
+
+ @Override
+ public final boolean hasAnyRole(String... roles) {
+ return hasAnyAuthorityName(defaultRolePrefix, roles);
+ }
+
+ private boolean hasAnyAuthorityName(String prefix, String... roles) {
+ final Set roleSet = getAuthoritySet();
+
+ for (final String role : roles) {
+ final String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
+ if (roleSet.contains(defaultedRole)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public final Authentication getAuthentication() {
+ return authentication;
+ }
+
+ @Override
+ public final boolean permitAll() {
+ return true;
+ }
+
+ @Override
+ public final boolean denyAll() {
+ return false;
+ }
+
+ @Override
+ public final boolean isAnonymous() {
+ return trustResolver.isAnonymous(authentication);
+ }
+
+ @Override
+ public final boolean isAuthenticated() {
+ return !isAnonymous();
+ }
+
+ @Override
+ public final boolean isRememberMe() {
+ return trustResolver.isRememberMe(authentication);
+ }
+
+ @Override
+ public final boolean isFullyAuthenticated() {
+ return !trustResolver.isAnonymous(authentication) && !trustResolver.isRememberMe(authentication);
+ }
+
+ public Object getPrincipal() {
+ return authentication.getPrincipal();
+ }
+
+ public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
+ this.trustResolver = trustResolver;
+ }
+
+ public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
+ this.roleHierarchy = roleHierarchy;
+ }
+
+ public void setDefaultRolePrefix(String defaultRolePrefix) {
+ this.defaultRolePrefix = defaultRolePrefix;
+ }
+
+ private Set getAuthoritySet() {
+ if (roles == null) {
+ roles = new HashSet();
+ Collection extends GrantedAuthority> userAuthorities = authentication.getAuthorities();
+
+ if (roleHierarchy != null) {
+ userAuthorities = roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
+ }
+
+ roles = AuthorityUtils.authorityListToSet(userAuthorities);
+ }
+
+ return roles;
+ }
+
+ @Override
+ public boolean hasPermission(Object target, Object permission) {
+ return permissionEvaluator.hasPermission(authentication, target, permission);
+ }
+
+ @Override
+ public boolean hasPermission(Object targetId, String targetType, Object permission) {
+ return permissionEvaluator.hasPermission(authentication, (Serializable) targetId, targetType, permission);
+ }
+
+ public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
+ this.permissionEvaluator = permissionEvaluator;
+ }
+
+ private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
+ if (role == null) {
+ return role;
+ }
+ if ((defaultRolePrefix == null) || (defaultRolePrefix.length() == 0)) {
+ return role;
+ }
+ if (role.startsWith(defaultRolePrefix)) {
+ return role;
+ }
+ return defaultRolePrefix + role;
+ }
+
+ @Override
+ public Object getFilterObject() {
+ return this.filterObject;
+ }
+
+ @Override
+ public Object getReturnObject() {
+ return this.returnObject;
+ }
+
+ @Override
+ public Object getThis() {
+ return this;
+ }
+
+ @Override
+ public void setFilterObject(Object obj) {
+ this.filterObject = obj;
+ }
+
+ @Override
+ public void setReturnObject(Object obj) {
+ this.returnObject = obj;
+ }
+}
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/security/MyUserDetailsService.java b/spring-security-mvc-boot/src/main/java/org/baeldung/security/MyUserDetailsService.java
new file mode 100644
index 0000000000..685219728f
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/security/MyUserDetailsService.java
@@ -0,0 +1,31 @@
+package org.baeldung.security;
+
+import org.baeldung.persistence.dao.UserRepository;
+import org.baeldung.persistence.model.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MyUserDetailsService implements UserDetailsService {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ public MyUserDetailsService() {
+ super();
+ }
+
+ // API
+
+ @Override
+ public UserDetails loadUserByUsername(final String username) {
+ final User user = userRepository.findByUsername(username);
+ if (user == null) {
+ throw new UsernameNotFoundException(username);
+ }
+ return new MyUserPrincipal(user);
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/security/MyUserPrincipal.java b/spring-security-mvc-boot/src/main/java/org/baeldung/security/MyUserPrincipal.java
new file mode 100644
index 0000000000..437bb02cdb
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/security/MyUserPrincipal.java
@@ -0,0 +1,72 @@
+package org.baeldung.security;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.baeldung.persistence.model.Privilege;
+import org.baeldung.persistence.model.User;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+public class MyUserPrincipal implements UserDetails {
+
+ private static final long serialVersionUID = 1L;
+
+ private final User user;
+
+ //
+
+ public MyUserPrincipal(User user) {
+ this.user = user;
+ }
+
+ //
+
+ @Override
+ public String getUsername() {
+ return user.getUsername();
+ }
+
+ @Override
+ public String getPassword() {
+ return user.getPassword();
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ final List authorities = new ArrayList();
+ for (final Privilege privilege : user.getPrivileges()) {
+ authorities.add(new SimpleGrantedAuthority(privilege.getName()));
+ }
+ return authorities;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ //
+
+ public User getUser() {
+ return user;
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/voter/MinuteBasedVoter.java b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/MinuteBasedVoter.java
new file mode 100644
index 0000000000..2beda1e557
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/MinuteBasedVoter.java
@@ -0,0 +1,26 @@
+package org.baeldung.voter;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+
+public class MinuteBasedVoter implements AccessDecisionVoter {
+ @Override
+ public boolean supports(ConfigAttribute attribute) {
+ return true;
+ }
+
+ @Override
+ public boolean supports(Class clazz) {
+ return true;
+ }
+
+ @Override
+ public int vote(Authentication authentication, Object object, Collection collection) {
+ return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).filter(r -> "ROLE_USER".equals(r) && LocalDateTime.now().getMinute() % 2 != 0).findAny().map(s -> ACCESS_DENIED).orElseGet(() -> ACCESS_ABSTAIN);
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/voter/VoterApplication.java b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/VoterApplication.java
new file mode 100644
index 0000000000..c032a9634e
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/VoterApplication.java
@@ -0,0 +1,17 @@
+package org.baeldung.voter;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+
+@Configuration
+@EnableAutoConfiguration
+@ComponentScan(basePackages = { "org.baeldung.voter" })
+public class VoterApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(VoterApplication.class, args);
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/voter/VoterMvcConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/VoterMvcConfig.java
new file mode 100644
index 0000000000..e282c794d0
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/VoterMvcConfig.java
@@ -0,0 +1,18 @@
+package org.baeldung.voter;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+/**
+ * Created by ambrusadrianz on 30/09/2016.
+ */
+
+@Configuration
+public class VoterMvcConfig extends WebMvcConfigurerAdapter {
+ @Override
+ public void addViewControllers(ViewControllerRegistry registry) {
+ registry.addViewController("/").setViewName("private");
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/voter/WebSecurityConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/WebSecurityConfig.java
new file mode 100644
index 0000000000..495567c7b2
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/WebSecurityConfig.java
@@ -0,0 +1,48 @@
+package org.baeldung.voter;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.vote.AuthenticatedVoter;
+import org.springframework.security.access.vote.RoleVoter;
+import org.springframework.security.access.vote.UnanimousBased;
+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;
+import org.springframework.security.web.access.expression.WebExpressionVoter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import java.util.Arrays;
+import java.util.List;
+
+//@Configuration
+//@EnableWebSecurity
+public class WebSecurityConfig 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
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter: off
+ http
+ // needed so our login could work
+ .csrf().disable().authorizeRequests().anyRequest().authenticated().accessDecisionManager(accessDecisionManager()).antMatchers("/").hasAnyRole("ROLE_ADMIN", "ROLE_USER").and().formLogin().permitAll().and().logout().permitAll()
+ .deleteCookies("JSESSIONID").logoutSuccessUrl("/login");
+ // @formatter: on
+ }
+
+ @Bean
+ public AccessDecisionManager accessDecisionManager() {
+ // @formatter: off
+ List> decisionVoters = Arrays.asList(new WebExpressionVoter(), new RoleVoter(), new AuthenticatedVoter(), new MinuteBasedVoter());
+ // @formatter: on
+ return new UnanimousBased(decisionVoters);
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/voter/XmlSecurityConfig.java b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/XmlSecurityConfig.java
new file mode 100644
index 0000000000..124513d317
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/voter/XmlSecurityConfig.java
@@ -0,0 +1,15 @@
+package org.baeldung.voter;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportResource;
+
+/**
+ * Created by ambrusadrianz on 09/10/2016.
+ */
+@Configuration
+@ImportResource({ "classpath:spring-security.xml" })
+public class XmlSecurityConfig {
+ public XmlSecurityConfig() {
+ super();
+ }
+}
diff --git a/spring-security-mvc-boot/src/main/java/org/baeldung/web/MainController.java b/spring-security-mvc-boot/src/main/java/org/baeldung/web/MainController.java
new file mode 100644
index 0000000000..4752f7bdd9
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/java/org/baeldung/web/MainController.java
@@ -0,0 +1,58 @@
+package org.baeldung.web;
+
+import org.baeldung.persistence.dao.OrganizationRepository;
+import org.baeldung.persistence.model.Foo;
+import org.baeldung.persistence.model.Organization;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@Controller
+public class MainController {
+
+ @Autowired
+ private OrganizationRepository organizationRepository;
+
+ // @PostAuthorize("hasPermission(returnObject, 'read')")
+ @PreAuthorize("hasPermission(#id, 'Foo', 'read')")
+ @RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
+ @ResponseBody
+ public Foo findById(@PathVariable final long id) {
+ return new Foo("Sample");
+ }
+
+ @PreAuthorize("hasPermission(#foo, 'write')")
+ @RequestMapping(method = RequestMethod.POST, value = "/foos")
+ @ResponseStatus(HttpStatus.CREATED)
+ @ResponseBody
+ public Foo create(@RequestBody final Foo foo) {
+ return foo;
+ }
+
+ //
+
+ @PreAuthorize("hasAuthority('FOO_READ_PRIVILEGE')")
+ @RequestMapping(method = RequestMethod.GET, value = "/foos")
+ @ResponseBody
+ public Foo findFooByName(@RequestParam final String name) {
+ return new Foo(name);
+ }
+
+ //
+
+ @PreAuthorize("isMember(#id)")
+ @RequestMapping(method = RequestMethod.GET, value = "/organizations/{id}")
+ @ResponseBody
+ public Organization findOrgById(@PathVariable final long id) {
+ return organizationRepository.findOne(id);
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/main/resources/application.properties b/spring-security-mvc-boot/src/main/resources/application.properties
new file mode 100644
index 0000000000..d29b5f6bf1
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/application.properties
@@ -0,0 +1,10 @@
+server.port=8082
+server.context-path=/spring-security-mvc-boot
+spring.datasource.driver-class-name=org.h2.Driver
+spring.datasource.url=jdbc:h2:mem:security_permission;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+spring.datasource.username=sa
+spring.datasource.password=
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.database=H2
+spring.jpa.show-sql=false
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/persistence-derby.properties b/spring-security-mvc-boot/src/main/resources/persistence-derby.properties
new file mode 100644
index 0000000000..e808fdc288
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/persistence-derby.properties
@@ -0,0 +1,12 @@
+# jdbc.X
+jdbc.driverClassName=org.apache.derby.jdbc.EmbeddedDriver
+jdbc.url=jdbc:derby:memory:spring_custom_user_service;create=true
+jdbc.user=tutorialuser
+jdbc.pass=tutorialpass
+
+# hibernate.X
+hibernate.dialect=org.hibernate.dialect.DerbyDialect
+hibernate.show_sql=false
+hibernate.hbm2ddl.auto=create
+hibernate.cache.use_second_level_cache=false
+hibernate.cache.use_query_cache=false
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/spring-security.xml b/spring-security-mvc-boot/src/main/resources/spring-security.xml
new file mode 100644
index 0000000000..83bc14dda7
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/spring-security.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/templates/403.html b/spring-security-mvc-boot/src/main/resources/templates/403.html
new file mode 100644
index 0000000000..20550768cf
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/templates/403.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+You do not have permission to view this page.
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/templates/adminPage.html b/spring-security-mvc-boot/src/main/resources/templates/adminPage.html
new file mode 100644
index 0000000000..13aff9b15a
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/templates/adminPage.html
@@ -0,0 +1,13 @@
+
+
+
+
+Insert title here
+
+
+Welcome admin! Logout
+
+
+Back to links
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/templates/index.html b/spring-security-mvc-boot/src/main/resources/templates/index.html
new file mode 100644
index 0000000000..8e7394ad6a
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/templates/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+Spring Security Thymeleaf
+
+
+
+
+
+
+ Welcome
+
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/templates/login.html b/spring-security-mvc-boot/src/main/resources/templates/login.html
new file mode 100644
index 0000000000..dd6bd04767
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/templates/login.html
@@ -0,0 +1,27 @@
+
+
+
+
+ Login
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/templates/loginAdmin.html b/spring-security-mvc-boot/src/main/resources/templates/loginAdmin.html
new file mode 100644
index 0000000000..43d0e73233
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/templates/loginAdmin.html
@@ -0,0 +1,31 @@
+
+
+
+
+Insert title here
+
+
+
+ Admin login page
+
+
+ Login failed!
+
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/templates/loginUser.html b/spring-security-mvc-boot/src/main/resources/templates/loginUser.html
new file mode 100644
index 0000000000..bf4ddd48bc
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/templates/loginUser.html
@@ -0,0 +1,30 @@
+
+
+
+
+Login
+
+
+
+ User login page
+
+
+ Login failed!
+
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/templates/private.html b/spring-security-mvc-boot/src/main/resources/templates/private.html
new file mode 100644
index 0000000000..5af8c7a13e
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/templates/private.html
@@ -0,0 +1,10 @@
+
+
+
+ Private
+
+
+Congrats!
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/templates/protectedLinks.html b/spring-security-mvc-boot/src/main/resources/templates/protectedLinks.html
new file mode 100644
index 0000000000..1877464fe5
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/templates/protectedLinks.html
@@ -0,0 +1,13 @@
+
+
+
+
+Insert title here
+
+
+
+User page
+
+Admin page
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/main/resources/templates/userPage.html b/spring-security-mvc-boot/src/main/resources/templates/userPage.html
new file mode 100644
index 0000000000..894a225871
--- /dev/null
+++ b/spring-security-mvc-boot/src/main/resources/templates/userPage.html
@@ -0,0 +1,12 @@
+
+
+
+
+Insert title here
+
+
+Welcome user! Logout
+
+Back to links
+
+
\ No newline at end of file
diff --git a/spring-security-mvc-boot/src/test/java/org/baeldung/web/CustomUserDetailsServiceIntegrationTest.java b/spring-security-mvc-boot/src/test/java/org/baeldung/web/CustomUserDetailsServiceIntegrationTest.java
new file mode 100644
index 0000000000..616e80e6e9
--- /dev/null
+++ b/spring-security-mvc-boot/src/test/java/org/baeldung/web/CustomUserDetailsServiceIntegrationTest.java
@@ -0,0 +1,76 @@
+package org.baeldung.web;
+
+import static org.junit.Assert.assertEquals;
+
+import org.baeldung.config.MvcConfig;
+import org.baeldung.config.PersistenceConfig;
+import org.baeldung.config.SecurityConfig;
+import org.baeldung.persistence.dao.UserRepository;
+import org.baeldung.persistence.model.User;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = { MvcConfig.class, SecurityConfig.class, PersistenceConfig.class })
+@WebAppConfiguration
+public class CustomUserDetailsServiceIntegrationTest {
+
+ public static final String USERNAME = "user";
+ public static final String PASSWORD = "pass";
+ public static final String USERNAME2 = "user2";
+
+ @Autowired
+ UserRepository myUserRepository;
+
+ @Autowired
+ AuthenticationProvider authenticationProvider;
+
+ @Autowired
+ PasswordEncoder passwordEncoder;
+
+ //
+
+ @Test
+ public void givenExistingUser_whenAuthenticate_thenRetrieveFromDb() {
+ User user = new User();
+ user.setUsername(USERNAME);
+ user.setPassword(passwordEncoder.encode(PASSWORD));
+
+ myUserRepository.save(user);
+
+ UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(USERNAME, PASSWORD);
+ Authentication authentication = authenticationProvider.authenticate(auth);
+
+ assertEquals(authentication.getName(), USERNAME);
+ }
+
+ @Test(expected = BadCredentialsException.class)
+ public void givenIncorrectUser_whenAuthenticate_thenBadCredentialsException() {
+ User user = new User();
+ user.setUsername(USERNAME);
+ user.setPassword(passwordEncoder.encode(PASSWORD));
+
+ myUserRepository.save(user);
+
+ UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(USERNAME2, PASSWORD);
+ authenticationProvider.authenticate(auth);
+ }
+
+ //
+
+ @After
+ public void tearDown() {
+ myUserRepository.removeUserByUsername(USERNAME);
+ }
+
+}
diff --git a/spring-security-mvc-boot/src/test/java/org/baeldung/web/LiveTest.java b/spring-security-mvc-boot/src/test/java/org/baeldung/web/LiveTest.java
new file mode 100644
index 0000000000..47626b814a
--- /dev/null
+++ b/spring-security-mvc-boot/src/test/java/org/baeldung/web/LiveTest.java
@@ -0,0 +1,67 @@
+package org.baeldung.web;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.baeldung.persistence.model.Foo;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+
+import com.jayway.restassured.RestAssured;
+import com.jayway.restassured.authentication.FormAuthConfig;
+import com.jayway.restassured.response.Response;
+import com.jayway.restassured.specification.RequestSpecification;
+
+public class LiveTest {
+
+ private final FormAuthConfig formAuthConfig = new FormAuthConfig("http://localhost:8082/spring-security-custom-permission/login", "username", "password");
+
+ @Test
+ public void givenUserWithReadPrivilegeAndHasPermission_whenGetFooById_thenOK() {
+ final Response response = givenAuth("john", "123").get("http://localhost:8082/spring-security-custom-permission/foos/1");
+ assertEquals(200, response.getStatusCode());
+ assertTrue(response.asString().contains("id"));
+ }
+
+ @Test
+ public void givenUserWithNoWritePrivilegeAndHasPermission_whenPostFoo_thenForbidden() {
+ final Response response = givenAuth("john", "123").contentType(MediaType.APPLICATION_JSON_VALUE).body(new Foo("sample")).post("http://localhost:8082/spring-security-custom-permission/foos");
+ assertEquals(403, response.getStatusCode());
+ }
+
+ @Test
+ public void givenUserWithWritePrivilegeAndHasPermission_whenPostFoo_thenOk() {
+ final Response response = givenAuth("tom", "111").contentType(MediaType.APPLICATION_JSON_VALUE).body(new Foo("sample")).post("http://localhost:8082/spring-security-custom-permission/foos");
+ assertEquals(201, response.getStatusCode());
+ assertTrue(response.asString().contains("id"));
+ }
+
+ //
+
+ @Test
+ public void givenUserMemberInOrganization_whenGetOrganization_thenOK() {
+ final Response response = givenAuth("john", "123").get("http://localhost:8082/spring-security-custom-permission/organizations/1");
+ assertEquals(200, response.getStatusCode());
+ assertTrue(response.asString().contains("id"));
+ }
+
+ @Test
+ public void givenUserMemberNotInOrganization_whenGetOrganization_thenForbidden() {
+ final Response response = givenAuth("john", "123").get("http://localhost:8082/spring-security-custom-permission/organizations/2");
+ assertEquals(403, response.getStatusCode());
+ }
+
+ //
+
+ @Test
+ public void givenDisabledSecurityExpression_whenGetFooByName_thenError() {
+ final Response response = givenAuth("john", "123").get("http://localhost:8082/spring-security-custom-permission/foos?name=sample");
+ assertEquals(500, response.getStatusCode());
+ assertTrue(response.asString().contains("method hasAuthority() not allowed"));
+ }
+
+ //
+ private RequestSpecification givenAuth(String username, String password) {
+ return RestAssured.given().auth().form(username, password, formAuthConfig);
+ }
+}
\ No newline at end of file