diff --git a/spring-boot-security/pom.xml b/spring-boot-security/pom.xml
index 8763c210c8..b10f8e6e47 100644
--- a/spring-boot-security/pom.xml
+++ b/spring-boot-security/pom.xml
@@ -10,9 +10,10 @@
Spring Boot Security Auto-Configuration
- com.baeldung
- parent-modules
- 1.0.0-SNAPSHOT
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.9.RELEASE
+
@@ -55,6 +56,11 @@
spring-security-test
test
+
+ org.springframework.security
+ spring-security-test
+ test
+
@@ -62,6 +68,16 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+ com.baeldung.springbootsecurity.basic_auth.SpringBootSecurityApplication
+
+
+
@@ -69,6 +85,7 @@
UTF-8
UTF-8
+ 1.8
diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/form_login/SpringBootSecurityApplication.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/form_login/SpringBootSecurityApplication.java
new file mode 100644
index 0000000000..9f4796e1f9
--- /dev/null
+++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/form_login/SpringBootSecurityApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.springbootsecurity.form_login;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication(scanBasePackages = "com.baeldung.springbootsecurity.form_login")
+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/form_login/configuration/SecurityConfiguration.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/form_login/configuration/SecurityConfiguration.java
new file mode 100644
index 0000000000..1f5c53eb2b
--- /dev/null
+++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/form_login/configuration/SecurityConfiguration.java
@@ -0,0 +1,40 @@
+package com.baeldung.springbootsecurity.form_login.configuration;
+
+import com.baeldung.springbootsecurity.form_login.security.CustomAuthenticationFailureHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth
+ .inMemoryAuthentication()
+ .withUser("baeldung")
+ .password("baeldung")
+ .roles("USER");
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .authorizeRequests()
+ .anyRequest()
+ .authenticated()
+ .and()
+ .formLogin()
+ .failureHandler(customAuthenticationFailureHandler());
+ }
+
+ @Bean
+ public AuthenticationFailureHandler customAuthenticationFailureHandler() {
+ return new CustomAuthenticationFailureHandler();
+ }
+}
diff --git a/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/form_login/security/CustomAuthenticationFailureHandler.java b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/form_login/security/CustomAuthenticationFailureHandler.java
new file mode 100644
index 0000000000..2617c1f475
--- /dev/null
+++ b/spring-boot-security/src/main/java/com/baeldung/springbootsecurity/form_login/security/CustomAuthenticationFailureHandler.java
@@ -0,0 +1,28 @@
+package com.baeldung.springbootsecurity.form_login.security;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Override
+ public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ Map data = new HashMap<>();
+ data.put("timestamp", Calendar.getInstance().getTime());
+ data.put("exception", exception.getMessage());
+
+ response.getOutputStream().println(objectMapper.writeValueAsString(data));
+ }
+}
diff --git a/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/form_login/FormLoginIntegrationTest.java b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/form_login/FormLoginIntegrationTest.java
new file mode 100644
index 0000000000..d5b5d8637b
--- /dev/null
+++ b/spring-boot-security/src/test/java/com/baeldung/springbootsecurity/form_login/FormLoginIntegrationTest.java
@@ -0,0 +1,87 @@
+package com.baeldung.springbootsecurity.form_login;
+
+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.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import javax.servlet.Filter;
+
+import static org.junit.Assert.assertTrue;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = com.baeldung.springbootsecurity.form_login.SpringBootSecurityApplication.class)
+public class FormLoginIntegrationTest {
+
+ @Autowired
+ private WebApplicationContext context;
+
+ @Autowired
+ private Filter springSecurityFilterChain;
+
+ private MockMvc mvc;
+
+ @Before
+ public void setup() {
+ mvc = MockMvcBuilders
+ .webAppContextSetup(context)
+ .addFilters(springSecurityFilterChain)
+ .build();
+ }
+
+ @Test
+ public void givenRequestWithoutSessionOrCsrfToken_shouldFailWith403() throws Exception {
+ mvc
+ .perform(post("/"))
+ .andExpect(status().isForbidden());
+ }
+
+ @Test
+ public void givenRequestWithInvalidCsrfToken_shouldFailWith403() throws Exception {
+ mvc
+ .perform(post("/").with(csrf().useInvalidToken()))
+ .andExpect(status().isForbidden());
+ }
+
+ @Test
+ public void givenRequestWithValidCsrfTokenAndWithoutSessionToken_shouldReceive302WithLocationHeaderToLoginPage() throws Exception {
+ MvcResult mvcResult = mvc.perform(post("/").with(csrf())).andReturn();
+ assertTrue(mvcResult.getResponse().getStatus() == 302);
+ assertTrue(mvcResult.getResponse().getHeader("Location").contains("login"));
+ }
+
+ @Test
+ public void givenValidRequestWithValidCredentials_shouldLoginSuccessfully() throws Exception {
+ mvc
+ .perform(formLogin().user("baeldung").password("baeldung"))
+ .andExpect(status().isFound())
+ .andExpect(redirectedUrl("/"))
+ .andExpect(authenticated().withUsername("baeldung"));
+ }
+
+ @Test
+ public void givenValidRequestWithInvalidCredentials_shouldFailWith401() throws Exception {
+ MvcResult result = mvc
+ .perform(formLogin().user("random").password("random"))
+ .andExpect(status().isUnauthorized())
+ .andDo(print())
+ .andExpect(unauthenticated())
+ .andReturn();
+
+ assertTrue(result.getResponse().getContentAsString().contains("Bad credentials"));
+ }
+}