diff --git a/pom.xml b/pom.xml index 8906b1c8d2..19e42009c9 100644 --- a/pom.xml +++ b/pom.xml @@ -152,6 +152,8 @@ spring-boot spring-boot-keycloak spring-boot-bootstrap + spring-boot-admin + spring-boot-security spring-cloud-data-flow spring-cloud spring-core @@ -263,7 +265,6 @@ vertx-and-rxjava saas deeplearning4j - spring-boot-admin lucene vraptor diff --git a/spring-boot-security/README.md b/spring-boot-security/README.md new file mode 100644 index 0000000000..26ab8b2337 --- /dev/null +++ b/spring-boot-security/README.md @@ -0,0 +1,6 @@ +### Spring Boot Security Auto-Configuration + +- mvn clean install +- uncomment in application.properties spring.profiles.active=basic # for basic auth config +- uncomment in application.properties spring.profiles.active=form # for form based auth config +- uncomment actuator dependency simultaneously with the line from main class diff --git a/spring-boot-security/pom.xml b/spring-boot-security/pom.xml new file mode 100644 index 0000000000..c35191a7fc --- /dev/null +++ b/spring-boot-security/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + com.baeldung + spring-boot-security + 0.0.1-SNAPSHOT + jar + + spring-boot-security + Spring Boot Security Auto-Configuration + + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-dependencies + 1.5.9.RELEASE + pom + import + + + + + + UTF-8 + UTF-8 + 1.8 + + + + + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/SpringBootSecurityApplication.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/SpringBootSecurityApplication.java new file mode 100644 index 0000000000..3a85da72e5 --- /dev/null +++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/SpringBootSecurityApplication.java @@ -0,0 +1,16 @@ +package com.baeldung.springbootsecurity; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; + +@SpringBootApplication(exclude = { + SecurityAutoConfiguration.class + // ,ManagementWebSecurityAutoConfiguration.class +}) +public class SpringBootSecurityApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootSecurityApplication.class, args); + } +} diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/BasicConfiguration.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/BasicConfiguration.java new file mode 100644 index 0000000000..1b08e5ee22 --- /dev/null +++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/BasicConfiguration.java @@ -0,0 +1,37 @@ +package com.baeldung.springbootsecurity.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +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 +@Profile("basic") +public class BasicConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("user") + .password("password") + .roles("USER") + .and() + .withUser("admin") + .password("admin") + .roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .httpBasic(); + } +} diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/FormLoginConfiguration.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/FormLoginConfiguration.java new file mode 100644 index 0000000000..b4902a9ffc --- /dev/null +++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/FormLoginConfiguration.java @@ -0,0 +1,39 @@ +package com.baeldung.springbootsecurity.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +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 +@Profile("form") +public class FormLoginConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("user") + .password("password") + .roles("USER") + .and() + .withUser("admin") + .password("password") + .roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .formLogin() + .and() + .httpBasic(); + } +} diff --git a/spring-boot-security/src/main/resources/application.properties b/spring-boot-security/src/main/resources/application.properties new file mode 100644 index 0000000000..6ca2edb175 --- /dev/null +++ b/spring-boot-security/src/main/resources/application.properties @@ -0,0 +1,4 @@ +#spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration +#spring.profiles.active=form +#spring.profiles.active=basic +#security.user.password=password \ No newline at end of file diff --git a/spring-boot-security/src/main/resources/static/index.html b/spring-boot-security/src/main/resources/static/index.html new file mode 100644 index 0000000000..5e3506dde6 --- /dev/null +++ b/spring-boot-security/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + + Index + + +Welcome to Baeldung Secured Page !!! + + \ No newline at end of file diff --git a/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/BasicConfigurationIntegrationTest.java b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/BasicConfigurationIntegrationTest.java new file mode 100644 index 0000000000..63e1c2ac73 --- /dev/null +++ b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/BasicConfigurationIntegrationTest.java @@ -0,0 +1,56 @@ +package com.baeldung.springbootsecurity; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT) +@ActiveProfiles("basic") +public class BasicConfigurationIntegrationTest { + + TestRestTemplate restTemplate; + URL base; + + @LocalServerPort int port; + + @Before + public void setUp() throws MalformedURLException { + restTemplate = new TestRestTemplate("user", "password"); + base = new URL("http://localhost:" + port); + } + + @Test + public void whenLoggedUserRequestsHomePage_ThenSuccess() throws IllegalStateException, IOException { + ResponseEntity response = restTemplate.getForEntity(base.toString(), String.class); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response + .getBody() + .contains("Baeldung")); + } + + @Test + public void whenUserWithWrongCredentialsRequestsHomePage_ThenUnauthorizedPage() throws IllegalStateException, IOException { + restTemplate = new TestRestTemplate("user", "wrongpassword"); + ResponseEntity response = restTemplate.getForEntity(base.toString(), String.class); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertTrue(response + .getBody() + .contains("Unauthorized")); + } +} diff --git a/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/FormConfigurationIntegrationTest.java b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/FormConfigurationIntegrationTest.java new file mode 100644 index 0000000000..697a4f2868 --- /dev/null +++ b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/FormConfigurationIntegrationTest.java @@ -0,0 +1,106 @@ +package com.baeldung.springbootsecurity; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.*; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.junit.Assert.*; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT) +@ActiveProfiles("form") +public class FormConfigurationIntegrationTest { + + @Autowired TestRestTemplate restTemplate; + @LocalServerPort int port; + + @Test + public void whenLoginPageIsRequested_ThenSuccess() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setAccept(Collections.singletonList(MediaType.TEXT_HTML)); + ResponseEntity responseEntity = restTemplate.exchange("/login", HttpMethod.GET, new HttpEntity(httpHeaders), String.class); + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertTrue(responseEntity + .getBody() + .contains("_csrf")); + } + + @Test + public void whenTryingToLoginWithCorrectCredentials_ThenAuthenticateWithSuccess() { + HttpHeaders httpHeaders = getHeaders(); + httpHeaders.setAccept(Collections.singletonList(MediaType.TEXT_HTML)); + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap form = getFormSubmitCorrectCredentials(); + ResponseEntity responseEntity = this.restTemplate.exchange("/login", HttpMethod.POST, new HttpEntity<>(form, httpHeaders), String.class); + assertEquals(responseEntity.getStatusCode(), HttpStatus.FOUND); + assertTrue(responseEntity + .getHeaders() + .getLocation() + .toString() + .endsWith(this.port + "/")); + assertNotNull(responseEntity + .getHeaders() + .get("Set-Cookie")); + } + + @Test + public void whenTryingToLoginWithInorrectCredentials_ThenAuthenticationFailed() { + HttpHeaders httpHeaders = getHeaders(); + httpHeaders.setAccept(Collections.singletonList(MediaType.TEXT_HTML)); + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap form = getFormSubmitIncorrectCredentials(); + ResponseEntity responseEntity = this.restTemplate.exchange("/login", HttpMethod.POST, new HttpEntity<>(form, httpHeaders), String.class); + assertEquals(responseEntity.getStatusCode(), HttpStatus.FOUND); + assertTrue(responseEntity + .getHeaders() + .getLocation() + .toString() + .endsWith(this.port + "/login?error")); + assertNull(responseEntity + .getHeaders() + .get("Set-Cookie")); + } + + private MultiValueMap getFormSubmitCorrectCredentials() { + MultiValueMap form = new LinkedMultiValueMap<>(); + form.set("username", "user"); + form.set("password", "password"); + return form; + } + + private MultiValueMap getFormSubmitIncorrectCredentials() { + MultiValueMap form = new LinkedMultiValueMap<>(); + form.set("username", "user"); + form.set("password", "wrongpassword"); + return form; + } + + private HttpHeaders getHeaders() { + HttpHeaders headers = new HttpHeaders(); + ResponseEntity page = this.restTemplate.getForEntity("/login", String.class); + assertEquals(page.getStatusCode(), HttpStatus.OK); + String cookie = page + .getHeaders() + .getFirst("Set-Cookie"); + headers.set("Cookie", cookie); + Pattern pattern = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*"); + Matcher matcher = pattern.matcher(page.getBody()); + assertTrue(matcher.matches()); + headers.set("X-CSRF-TOKEN", matcher.group(1)); + return headers; + } + +}