From 37ccb60f1d312e3de9049569e2d8c15c886fa3f7 Mon Sep 17 00:00:00 2001 From: ambrusadrianz Date: Sat, 22 Oct 2016 15:50:19 +0300 Subject: [PATCH] [BAEL-123] Created an example about custom AccessDecisionVoters in Spring Security. (#728) * Created an example about custom AccessDecisionVoters in Spring Security. * Added module in root pom file. * Added test cases --- pom.xml | 1 + spring-security-custom-voter/pom.xml | 96 +++++++++++++++++++ .../main/java/org/baeldung/Application.java | 12 +++ .../baeldung/security/MinuteBasedVoter.java | 38 ++++++++ .../baeldung/security/WebSecurityConfig.java | 69 +++++++++++++ .../baeldung/security/XmlSecurityConfig.java | 15 +++ .../main/java/org/baeldung/web/MvcConfig.java | 18 ++++ .../src/main/resources/spring-security.xml | 40 ++++++++ .../src/main/resources/templates/private.html | 10 ++ .../src/main/webapp/WEB-INF/web.xml | 47 +++++++++ .../test/java/org/baeldung/web/LiveTest.java | 42 ++++++++ 11 files changed, 388 insertions(+) create mode 100644 spring-security-custom-voter/pom.xml create mode 100644 spring-security-custom-voter/src/main/java/org/baeldung/Application.java create mode 100644 spring-security-custom-voter/src/main/java/org/baeldung/security/MinuteBasedVoter.java create mode 100644 spring-security-custom-voter/src/main/java/org/baeldung/security/WebSecurityConfig.java create mode 100644 spring-security-custom-voter/src/main/java/org/baeldung/security/XmlSecurityConfig.java create mode 100644 spring-security-custom-voter/src/main/java/org/baeldung/web/MvcConfig.java create mode 100644 spring-security-custom-voter/src/main/resources/spring-security.xml create mode 100644 spring-security-custom-voter/src/main/resources/templates/private.html create mode 100644 spring-security-custom-voter/src/main/webapp/WEB-INF/web.xml create mode 100644 spring-security-custom-voter/src/test/java/org/baeldung/web/LiveTest.java diff --git a/pom.xml b/pom.xml index eee9f07ab9..9979ff95a5 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,7 @@ spring-security-basic-auth spring-security-custom-permission + spring-security-custom-voter spring-security-mvc-custom spring-security-mvc-digest-auth spring-security-mvc-ldap diff --git a/spring-security-custom-voter/pom.xml b/spring-security-custom-voter/pom.xml new file mode 100644 index 0000000000..727efa48ab --- /dev/null +++ b/spring-security-custom-voter/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + org.baeldung + spring-security-custom-voter + 0.0.1-SNAPSHOT + war + + spring-security-custom-voter + Custom AccessDecisionVoter with Spring Security + + + org.springframework.boot + spring-boot-starter-parent + 1.4.1.RELEASE + + + + + UTF-8 + UTF-8 + 3.0.1 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + junit + junit + test + + + + org.hamcrest + hamcrest-core + test + + + + org.hamcrest + hamcrest-library + test + + + + org.springframework + spring-test + 4.3.3.RELEASE + + + + org.springframework.security + spring-security-test + 4.1.3.RELEASE + test + + + + org.springframework.security + spring-security-web + 4.1.3.RELEASE + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-security-custom-voter/src/main/java/org/baeldung/Application.java b/spring-security-custom-voter/src/main/java/org/baeldung/Application.java new file mode 100644 index 0000000000..a9d6f3b8b1 --- /dev/null +++ b/spring-security-custom-voter/src/main/java/org/baeldung/Application.java @@ -0,0 +1,12 @@ +package org.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-security-custom-voter/src/main/java/org/baeldung/security/MinuteBasedVoter.java b/spring-security-custom-voter/src/main/java/org/baeldung/security/MinuteBasedVoter.java new file mode 100644 index 0000000000..8d22c52b0d --- /dev/null +++ b/spring-security-custom-voter/src/main/java/org/baeldung/security/MinuteBasedVoter.java @@ -0,0 +1,38 @@ +package org.baeldung.security; + +import org.springframework.security.access.AccessDecisionVoter; +import org.springframework.security.access.ConfigAttribute; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +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) { + List roles = authentication + .getAuthorities() + .stream().map(GrantedAuthority::getAuthority) + .collect(Collectors.toList()); + + for (String role: roles) { + if ("ROLE_USER".equals(role) && LocalDateTime.now().getMinute() % 2 != 0) { + return ACCESS_DENIED; + } + } + return ACCESS_ABSTAIN; + } +} diff --git a/spring-security-custom-voter/src/main/java/org/baeldung/security/WebSecurityConfig.java b/spring-security-custom-voter/src/main/java/org/baeldung/security/WebSecurityConfig.java new file mode 100644 index 0000000000..b3fb196424 --- /dev/null +++ b/spring-security-custom-voter/src/main/java/org/baeldung/security/WebSecurityConfig.java @@ -0,0 +1,69 @@ +package org.baeldung.security; + +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-custom-voter/src/main/java/org/baeldung/security/XmlSecurityConfig.java b/spring-security-custom-voter/src/main/java/org/baeldung/security/XmlSecurityConfig.java new file mode 100644 index 0000000000..45e095c66e --- /dev/null +++ b/spring-security-custom-voter/src/main/java/org/baeldung/security/XmlSecurityConfig.java @@ -0,0 +1,15 @@ +package org.baeldung.security; + +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-custom-voter/src/main/java/org/baeldung/web/MvcConfig.java b/spring-security-custom-voter/src/main/java/org/baeldung/web/MvcConfig.java new file mode 100644 index 0000000000..5d38dca1ff --- /dev/null +++ b/spring-security-custom-voter/src/main/java/org/baeldung/web/MvcConfig.java @@ -0,0 +1,18 @@ +package org.baeldung.web; + +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 MvcConfig extends WebMvcConfigurerAdapter { + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("private"); + } +} diff --git a/spring-security-custom-voter/src/main/resources/spring-security.xml b/spring-security-custom-voter/src/main/resources/spring-security.xml new file mode 100644 index 0000000000..117638289e --- /dev/null +++ b/spring-security-custom-voter/src/main/resources/spring-security.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-security-custom-voter/src/main/resources/templates/private.html b/spring-security-custom-voter/src/main/resources/templates/private.html new file mode 100644 index 0000000000..5af8c7a13e --- /dev/null +++ b/spring-security-custom-voter/src/main/resources/templates/private.html @@ -0,0 +1,10 @@ + + + + Private + + +

Congrats!

+ + \ No newline at end of file diff --git a/spring-security-custom-voter/src/main/webapp/WEB-INF/web.xml b/spring-security-custom-voter/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..d69bce35ae --- /dev/null +++ b/spring-security-custom-voter/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,47 @@ + + + + Spring Secured Application + + + + mvc + org.springframework.web.servlet.DispatcherServlet + 1 + + + mvc + / + + + + contextClass + + org.springframework.web.context.support.AnnotationConfigWebApplicationContext + + + + contextConfigLocation + org.baeldung.spring.web.config + + + + org.springframework.web.context.ContextLoaderListener + + + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /* + + + \ No newline at end of file diff --git a/spring-security-custom-voter/src/test/java/org/baeldung/web/LiveTest.java b/spring-security-custom-voter/src/test/java/org/baeldung/web/LiveTest.java new file mode 100644 index 0000000000..3ddeab5920 --- /dev/null +++ b/spring-security-custom-voter/src/test/java/org/baeldung/web/LiveTest.java @@ -0,0 +1,42 @@ +package org.baeldung.web; + +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.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +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.SecurityMockMvcRequestBuilders.formLogin; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @WebAppConfiguration @SpringBootTest public class LiveTest { + @Autowired private WebApplicationContext context; + + private MockMvc mvc; + + @Before public void setup() { + mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build(); + } + + @Test public void givenUnauthenticatedUser_whenAccessingMainPage_thenRedirect() throws Exception { + mvc.perform(get("/")).andExpect(status().is3xxRedirection()); + } + + @Test public void givenValidUsernameAndPassword_whenLogin_thenOK() throws Exception { + mvc.perform(formLogin("/login").user("user").password("pass")).andExpect(authenticated()); + } + + @Test public void givenAuthenticatedAdmin_whenAccessingMainPage_thenOK() throws Exception { + mvc.perform(get("/").with(user("admin").password("pass").roles("ADMIN"))).andExpect(status().isOk()); + } +}