- Spring Security Tutorial
- Sprint Security - Home
- Spring Security - Introduction
- Spring Security - Architecture
- Spring Security - Project Modules
- Spring Security - Environment Setup
- Spring Security - Form Login
- Spring Security - Custom Form Login
- Spring Security - Logout
- Spring Security - Remember Me
- Spring Security - Redirection
- Spring Security - Taglibs
- Spring Security - XML Configuration
- Spring Security Useful Resources
- Spring Security - Quick Guide
- Spring Security - Useful Resources
- Spring Security - Discussion
Spring Security - Custom Form Login
Form-based login is one form of Username/password authentication that Spring Security provides support for. This is provided through an Html form.
Whenever a user requests a protected resource, Spring Security checks for the authentication of the request. If the request is not authenticated/authorized, the user will be redirected to the login page. The login page must be somehow rendered by the application. Spring Security provides that login form by default as we've seen in Spring Security - Form Login chapter.
In most of the production cases, login pages are customized and must be explicitly provided as given below −
protected void configure(HttpSecurity http) throws Exception { http // ... .authorizeHttpRequests( request -> request.requestMatchers("/login").permitAll() .requestMatchers("/**").authenticated() ) .formLogin(form -> form.loginPage("/login").permitAll()) }
Let us start actual programming with Spring Security. Before you start writing your example using Spring framework, you have to make sure that you have set up your Spring environment properly as explained in Spring Security - Environment Setup Chapter. We also assume that you have a bit of working knowledge on Spring Tool Suite IDE.
Now let us proceed to write a Spring MVC based Application managed by Maven, which will ask user to login, authenticate user and then provide option to logout using Spring Security Form Login Feature.
Create Project using Spring Initializr
Spring Initializr is great way to start with Spring Boot project. It provides a easy to use User Interface to create a project, add dependencies, select java runtime etc. It generates a skeleton project structure which once downloaded can be imported in spring tool suite and we can proceed with our readymade project structure.
We're choosing a maven project, naming the project as formlogin, with java version as 21. Following dependencies are added:
Spring Web
Spring Security
Spring Boot DevTools
Thymeleaf is a templating engine for Java. It allows us to quickly develop static or dynamic web pages for rendering in the browser. It is extremely extensible and allows us to define and customize the processing of our templates in fine detail. In addition to this, we can learn more about Thymeleaf by clicking this link.
Let's move on to generate our project and download it. We then extract it to a folder of our choice and use any IDE to open it. I shall be using Spring Tools Suite 4. It is available for free downloading from the https://spring.io/tools website and is optimized for spring applications.
pom.xml with all relevant dependencies
Let's take a look at our pom.xml file. It should look something similar to this −
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.tutorialspoint.security</groupId> <artifactId>formlogin</artifactId> <version>0.0.1-SNAPSHOT</version> <name>formlogin</name> <description>Demo project for Spring Boot</description> <url/> <licenses> <license/> </licenses> <developers> <developer/> </developers> <scm> <connection/> <developerConnection/> <tag/> <url/> </scm> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity6</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Login.html
This code requires a login.html file to be present in the mapped folder which would be returned on hitting the /login. This HTML file should contain a login form. Furthermore, the request should be a post request to /login. The parameter names should be "username" and "password" for username and password respectively.
Create login.html in /src/main/resources/templates folder with following content to act as a login page.
login.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Spring Security Example</title> </head> <body> <div th:if="${param.error}"> <p>Bad Credentials</p> </div> <div th:if="${param.logout}">You have been logged out.</div> <form th:action="@{/login}" method="post"> <div> <h1>Please sign in</h1> <label for="username"><b>Username</b></label> <input type="text" placeholder="Enter Username" name="username" id="username" required> <label for="password"><b>Password</b></label> <input type="password" placeholder="Enter Password" name="password" id="password" required> <input type="submit" value="Sign In" /> </div> </form> </body> </html>
Update Spring Security Configuration Class
Inside of our config package, we have WebSecurityConfig class as defined in Spring Security - Form Login chapter. Let's update its filterChain() method for our custom login page
http. //... .formLogin(form -> form.loginPage("/login") .defaultSuccessUrl("/") .failureUrl("/login?error=true") .permitAll()) //... .build();
Following is the complete code of the Spring Security Configuration class
WebSecurityConfig.java
package com.tutorialspoint.security.formlogin.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.configurers.AbstractHttpConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class WebSecurityConfig { @Bean protected UserDetailsService userDetailsService() { UserDetails user = User.builder() .username("user") .password(passwordEncoder().encode("user123")) .roles("USER") .build(); UserDetails admin = User.builder() .username("admin") .password(passwordEncoder().encode("admin123")) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } @Bean protected PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests( request -> request.requestMatchers("/login").permitAll() .requestMatchers("/**").authenticated() ) .formLogin(form -> form.loginPage("/login") .defaultSuccessUrl("/") .failureUrl("/login?error=true") .permitAll()) .logout(config -> config .logoutUrl("/logout") .logoutSuccessUrl("/login")) .build(); } }
There are a few points to note here −
defaultSuccessUrl ("/") − This endpoint will serve the index page as well as success page of our application. As we have configured earlier, we shall be protecting this page and allow only authenticated users will be able to access this page.
failureUrl ("/login?error=true") − This endpoint will load the login page with error flag to show the error message.
logoutUrl ("/logout") − This will be used to logout from our application.
logoutSuccessUrl ("/login") − This will be used to load login page once user is successfully logged out.
Controller Class
In this class, we've created a mapping for "/" endpoint and for "/login" for the index page and login page of this application.
AuthController.java
package com.tutorialspoint.security.formlogin.controllers; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class AuthController { @GetMapping("/") public String home() { return "index"; } @GetMapping("/login") public String login() { return "login"; } }
Views
Let's update index.html in /src/main/resources/templates folder with following content to act as a home page and to display logged in user name.
index.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6"> <head> <title> Hello World! </title> </head> <body> <h1 th:inline="text">Hello <span sec:authentication="name"></span>!</h1> <form th:action="@{/logout}" method="post"> <input type="submit" value="Sign Out"/> </form> </body> <html>
login.html
Let's create the login.html in /src/main/resources/templates folder with following content to act as a login page. We're using default name username and password for text fields. In case of other name, we need to set the same in spring security config class as well.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6"> <head> <title>Spring Security Example</title> </head> <body> <div th:if="${param.error}"> <p>Bad Credentials</p> </div> <div th:if="${param.logout}">You have been logged out.</div> <form th:action="@{/login}" method="post"> <div> <h1>Please sign in</h1> <label for="username"> <b>Username</b> </label> <input type="text" placeholder="Enter Username" name="username" id="username" required> <label for="password"><b>Password</b></label> <input type="password" placeholder="Enter Password" name="password" id="password" required> <input type="submit" value="Sign In" /> </div> </form> </body> </html>
In login.html, we're reading request parameter error using ${param.error}. If it is true, then an error message is printed as Bad Credential. Similarly, we're reading request parameter logout using ${param.logout}. If it is true, then logout message is printed.
In login form, we're using POST method to login while using input fields with name and id as username and password.
Running the Application
As we've all component ready, let's run the Application. Right Click on the project, select Run As and then Spring Boot App.
It will boot up the application and once application is started, we can run localhost:8080 to check the changes.
Output
Now open localhost:8080, you can see our custom login page.
Login Page
Login Page for Bad Credentials
Enter any invalid credential and it will show error.
Home Page
Enter valid credential
and it will load home page.
After Logout
Now click on sign-out button, which will load the login page again.