From 8de8770eecc255e4dfea382c6848c4add1190e53 Mon Sep 17 00:00:00 2001
From: Bogdan Stoean <4540392+BogdanStoean@users.noreply.github.com>
Date: Sun, 31 Dec 2017 17:04:37 +0200
Subject: [PATCH] [BAEL-1410] Spring Boot Security auto-configuration (#3329)
* [BAEL-1410] Spring Boot Security Auto-Configuration
* [BAEL-1410] Added some tests for incorrect credentials use case
* [BAEL-1410] Added readme and some code improvements
---
pom.xml | 3 +-
spring-boot-security/README.md | 6 +
spring-boot-security/pom.xml | 73 ++++++++++++
.../SpringBootSecurityApplication.java | 16 +++
.../config/BasicConfiguration.java | 37 ++++++
.../config/FormLoginConfiguration.java | 39 +++++++
.../src/main/resources/application.properties | 4 +
.../src/main/resources/static/index.html | 10 ++
.../BasicConfigurationIntegrationTest.java | 56 +++++++++
.../FormConfigurationIntegrationTest.java | 106 ++++++++++++++++++
10 files changed, 349 insertions(+), 1 deletion(-)
create mode 100644 spring-boot-security/README.md
create mode 100644 spring-boot-security/pom.xml
create mode 100644 spring-boot-security/src/main/java/com/baeldung/springbootsecurity/SpringBootSecurityApplication.java
create mode 100644 spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/BasicConfiguration.java
create mode 100644 spring-boot-security/src/main/java/com/baeldung/springbootsecurity/config/FormLoginConfiguration.java
create mode 100644 spring-boot-security/src/main/resources/application.properties
create mode 100644 spring-boot-security/src/main/resources/static/index.html
create mode 100644 spring-boot-security/src/test/java/com/baeldung/springbootsecurity/BasicConfigurationIntegrationTest.java
create mode 100644 spring-boot-security/src/test/java/com/baeldung/springbootsecurity/FormConfigurationIntegrationTest.java
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;
+ }
+
+}