Spring Security - Password Encoding



Prior to Spring Security 5, developers could use in memory password as plain text but with password related enhancements in spring security, now plain text password is not supported by spring security. In case, we need to achieve it intentionally then we need to use '{noop}' as prefix. Consider the following security configuration.

@Bean 
protected UserDetailsService userDetailsService() {
   UserDetails user = User.builder()
      .username("user")
      .password("{noop}user123") // password stored as plain text
      .roles("USER")
      .build();
   UserDetails admin = User.builder()
      .username("admin")
      .password("{noop}admin123")
      .roles("USER", "ADMIN") // password stored as plain text
      .build();
   return new InMemoryUserDetailsManager(user, admin);
}

Here Spring security is using NoOpPasswordEncoder as a default password encoder to validate the configured user. But this approach is not advisable for production as NoOpPasswordEncoder is deprecated and is insecure.

With Password Encoder

It is always advised to use a good password encoder to encrypt the in memory password. BCryptPasswordEncoder is one such inbuilt password encoder which password will be stored in memory in BCrypt encoded format.

@Bean 
protected UserDetailsService userDetailsService() {
   UserDetails user = User.builder()
      .username("user")
      .password(passwordEncoder().encode("user123")) // encode the password
      .roles("USER")
      .build();
   UserDetails admin = User.builder()
      .username("admin")
      .password(passwordEncoder().encode("admin123")) // encode the password
      .roles("USER", "ADMIN")
      .build();
   return new InMemoryUserDetailsManager(user, admin);
}

// Create a new Password Encoder
@Bean 
protected PasswordEncoder passwordEncoder() { 
   return new BCryptPasswordEncoder(); 
}

In case of persistent password storage, we should use the password encoder to encode the password and then store in the database.BCryptPasswordEncoder uses a random salt and creates an encoded password of length 60. Following is a sample of a BCrypt Encoded Password:

$2a$10$acaXGauv/3buNdwQWeOgu.iab3LLDclrH64xVMsSxd9Lp/otgUfMm

Here each field is divided by a $ symbol. Following are the details of each field.

  • 2a - represents the encoding algorithm as bcrypt

  • 10 - represents the strenth of the algorithm

  • first 22 characters - salt

  • remaining 31 characters - hashed version of plain text password

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

  • Thymeleaf

  • Spring Boot DevTools

Spring Initializr

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>

Spring Security Configuration Class

Inside of our config package, we have created the WebSecurityConfig class. We shall be using this class for our security configurations, so let's annotate it with an @Configuration annotation and @EnableWebSecurity. As a result, Spring Security knows to treat this class a configuration class. As we can see, configuring applications have been made very easy by Spring.

WebSecurityConfig

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")) // encode the password
         .roles("USER")
         .build();
      UserDetails admin = User.builder()
         .username("admin")
         .password(passwordEncoder().encode("admin123")) // encode the password
         .roles("USER", "ADMIN")
         .build();
      // print the encoded password
      System.out.println("Encoded Password for User: " + user.getPassword());
      return new InMemoryUserDetailsManager(user, admin);
   }

   // Password Encoder
   @Bean 
   protected PasswordEncoder passwordEncoder() { 
      return new BCryptPasswordEncoder(); 
   }

   // Security Configuration
   @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())       
         .rememberMe(config -> config.key("123456")
         .tokenValiditySeconds(3600))   
         .logout(config -> config  
            .logoutUrl("/logout") 
            .logoutSuccessUrl("/login")
			.invalidateHttpSession(true)
            .deleteCookies("JSESSIONID"))
         .build();
   }   
}

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 create 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-springsecurity3"> 
   <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, password and remember-me 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-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">
         <h1>Please sign in</h1>
         <table>
            <tr>
               <td><label for="username"><b>Username</b></label></td>
               <td><input type="text" placeholder="Enter Username" name="username" id="username" required></td>
            </tr>
            <tr>
               <td><label for="password"><b>Password</b></label></td>
               <td><input type="password" placeholder="Enter Password" name="password" id="password" required></td>
            </tr>
            <tr>
               <td><label for="remember-me"><b>Remember Me</b></label> </td>
               <td><input type="checkbox" name="remember-me" /></td>
            </tr>
            <tr>
               <td> </td>
               <td><input type="submit"  value="Sign In" /></td>
            </tr>		
         </table>       
      </form> 
   </body>
</html>

In login form, we're using POST method to login while using input fields with name and id as username, password and remember-me checkbox. If checkbox is selected, a remember-me Cookie will be created when user logs in the application.

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 the output in the eclipse console.

Output

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

[32m :: Spring Boot :: [39m              [2m (v3.3.1)[0;39m

[2m2024-08-09T15:44:25.364+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mc.t.s.formlogin.FormloginApplication    [0;39m [2m:[0;39m Starting FormloginApplication using Java 21.0.3 with PID 11300 (E:\security\formlogin\target\classes started by Tutorialspoint in E:\security\formlogin)
[2m2024-08-09T15:44:25.364+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mc.t.s.formlogin.FormloginApplication    [0;39m [2m:[0;39m No active profile set, falling back to 1 default profile: "default"
ut[2m2024-08-09T15:44:25.598+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.s.b.w.embedded.tomcat.TomcatWebServer [0;39m [2m:[0;39m Tomcat initialized with port 8080 (http)
[2m2024-08-09T15:44:25.600+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.apache.catalina.core.StandardService  [0;39m [2m:[0;39m Starting service [Tomcat]
[2m2024-08-09T15:44:25.600+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.apache.catalina.core.StandardEngine   [0;39m [2m:[0;39m Starting Servlet engine: [Apache Tomcat/10.1.25]
[2m2024-08-09T15:44:25.615+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.a.c.c.C.[Tomcat].[localhost].[/]      [0;39m [2m:[0;39m Initializing Spring embedded WebApplicationContext
[2m2024-08-09T15:44:25.615+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mw.s.c.ServletWebServerApplicationContext[0;39m [2m:[0;39m Root WebApplicationContext: initialization completed in 249 ms
[2m2024-08-09T15:44:25.845+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mr$InitializeUserDetailsManagerConfigurer[0;39m [2m:[0;39m Global AuthenticationManager configured with UserDetailsService bean with name userDetailsService
[2m2024-08-09T15:44:25.854+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.s.b.a.w.s.WelcomePageHandlerMapping   [0;39m [2m:[0;39m Adding welcome page template: index
[2m2024-08-09T15:44:25.928+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.s.b.d.a.OptionalLiveReloadServer      [0;39m [2m:[0;39m LiveReload server is running on port 35729
[2m2024-08-09T15:44:25.937+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.s.b.w.embedded.tomcat.TomcatWebServer [0;39m [2m:[0;39m Tomcat started on port 8080 (http) with context path '/'
[2m2024-08-09T15:44:25.940+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mc.t.s.formlogin.FormloginApplication    [0;39m [2m:[0;39m Started FormloginApplication in 0.597 seconds (process running for 14092.035)
[2m2024-08-09T15:44:25.942+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36m.ConditionEvaluationDeltaLoggingListener[0;39m [2m:[0;39m Condition evaluation unchanged
[2m2024-08-09T15:44:57.018+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [   File Watcher][0;39m [2m[0;39m[36mrtingClassPathChangeChangedEventListener[0;39m [2m:[0;39m Restarting due to 1 class path change (0 additions, 0 deletions, 1 modification)

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

[32m :: Spring Boot :: [39m              [2m (v3.3.1)[0;39m

[2m2024-08-09T15:44:57.123+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mc.t.s.formlogin.FormloginApplication    [0;39m [2m:[0;39m Starting FormloginApplication using Java 21.0.3 with PID 11300 (E:\security\formlogin\target\classes started by Tutorialspoint in E:\security\formlogin)
[2m2024-08-09T15:44:57.123+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mc.t.s.formlogin.FormloginApplication    [0;39m [2m:[0;39m No active profile set, falling back to 1 default profile: "default"
[2m2024-08-09T15:44:57.274+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.s.b.w.embedded.tomcat.TomcatWebServer [0;39m [2m:[0;39m Tomcat initialized with port 8080 (http)
[2m2024-08-09T15:44:57.275+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.apache.catalina.core.StandardService  [0;39m [2m:[0;39m Starting service [Tomcat]
[2m2024-08-09T15:44:57.275+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.apache.catalina.core.StandardEngine   [0;39m [2m:[0;39m Starting Servlet engine: [Apache Tomcat/10.1.25]
[2m2024-08-09T15:44:57.293+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.a.c.c.C.[Tomcat].[localhost].[/]      [0;39m [2m:[0;39m Initializing Spring embedded WebApplicationContext
[2m2024-08-09T15:44:57.293+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mw.s.c.ServletWebServerApplicationContext[0;39m [2m:[0;39m Root WebApplicationContext: initialization completed in 168 ms

Encoded Password for User: $2a$10$acaXGauv/3buNdwQWeOgu.iab3LLDclrH64xVMsSxd9Lp/otgUfMm

[2m2024-08-09T15:44:57.485+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mr$InitializeUserDetailsManagerConfigurer[0;39m [2m:[0;39m Global AuthenticationManager configured with UserDetailsService bean with name userDetailsService
[2m2024-08-09T15:44:57.489+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.s.b.a.w.s.WelcomePageHandlerMapping   [0;39m [2m:[0;39m Adding welcome page template: index
[2m2024-08-09T15:44:57.531+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.s.b.d.a.OptionalLiveReloadServer      [0;39m [2m:[0;39m LiveReload server is running on port 35729
[2m2024-08-09T15:44:57.539+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mo.s.b.w.embedded.tomcat.TomcatWebServer [0;39m [2m:[0;39m Tomcat started on port 8080 (http) with context path '/'
[2m2024-08-09T15:44:57.542+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36mc.t.s.formlogin.FormloginApplication    [0;39m [2m:[0;39m Started FormloginApplication in 0.439 seconds (process running for 14123.637)
[2m2024-08-09T15:44:57.543+05:30[0;39m [32m INFO[0;39m [35m11300[0;39m [2m---[0;39m [2m[formlogin] [  restartedMain][0;39m [2m[0;39m[36m.ConditionEvaluationDeltaLoggingListener[0;39m [2m:[0;39m Condition evaluation unchanged

Advertisements