1. Introduction
In this article, we are going to present Thymeleaf Input Password component showing password strength and providing show/hide option. The sample application will use Bootstrap framework and special JavaScript library password-strength-meter created by Òscar Casajuana. We will use a simple Spring Boot Security configuration to enable encrypting of the provided passwords.
For more information about Thymeleaf check below links:
Thymeleaf configuration with Spring Boot
Working with Forms in Thymeleaf
2. Dependencies
2.1. Maven dependencies
The application use three Maven dependencies:
2.2. Front-End libraries
Front-end layer use three libraries:
bootstrap - GUI framework for building responsive layouts,
jquery - JavaScript framework,
password-strength-meter - a JavaScript library for presenting password strength.
Maven pom.xml
file have the following structure:
Copy
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>thymeleaf-bootstrap-input-password</artifactId>
<properties>
<bootstrap.version>4.0.0-2</bootstrap.version>
<webjars-locator.version>0.30</webjars-locator.version>
<lombok.version>1.18.2</lombok.version>
<font-awesome.version>5.11.2</font-awesome.version>
</properties>
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>${bootstrap.version}</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>${webjars-locator.version}</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>font-awesome</artifactId>
<version>${font-awesome.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. Model, Controller and Main Application class
The sample application will be simulating user login operation. We used simple validations on the frontend only just to check how the Thymeleaf Input Password component will work.
The main Spring Boot application contains a single Bean
responsible for encrypting a provided password. The BCryptPasswordEncoder
bean comes from Spring Boot Security package (thats why we need spring-boot-starter-security dependency in pom.xml
)
The Application
class has the following structure:
Copy
package com.frontbackend.thymeleaf.bootstrap;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The application uses a single controller - IndexController to handle all GET and POSTS requests to the root context (/
). POST operation will take the User
object (that is our command object) entered in Thymeleaf form. During that operation, the password will be encrypted and shown on the result page.
The IndexController
have the following structure:
Copy
package com.frontbackend.thymeleaf.bootstrap.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.frontbackend.thymeleaf.bootstrap.model.User;
@Controller
@RequestMapping({ "/", "/index" })
public class IndexController {
private final PasswordEncoder passwordEncoder;
@Autowired
public IndexController(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@GetMapping
public String main(Model model) {
model.addAttribute("user", new User());
return "index";
}
@PostMapping
public String save(User user, Model model) {
if (!StringUtils.isEmpty(user.getPassword())) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
}
model.addAttribute("user", user);
return "saved";
}
}
In the model layer, we have a single User
class with two fields: username
and password
:
Copy
package com.frontbackend.thymeleaf.bootstrap.model;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@NoArgsConstructor
public class User {
private String username;
private String password;
}
Adding Spring Security dependency automatically enabling basic authorization. In this application, we just want to present the Thymeleaf Input Password component that's why we disabled basic HTTP authorization (.httpBasic().disable()
) and set all endpoints to be available without login (.anyRequest().permitAll()
).
Copy
package com.frontbackend.thymeleaf.bootstrap.config;
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.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity security) throws Exception {
security.csrf()
.disable()
.httpBasic()
.disable()
.authorizeRequests()
.anyRequest()
.permitAll();
}
}
4. Templates
Presentation layer contains two Thymeleaf templates:
index.html - main page service login form,
saved.html - template for presenting submitted values from the previous view.
The index.html
has the following structure:
Copy
<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Spring Boot Thymeleaf Application - Bootstrap Input Password</title>
<link th:rel="stylesheet" th:href="@{assets/password-strength-meter/password.min.css}"/>
<link th:rel="stylesheet" th:href="@{webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "/>
<link th:rel="stylesheet" th:href="@{webjars/font-awesome/5.11.2/css/all.css} "/>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container">
<a class="navbar-brand" href="/">Thymeleaf - Bootstrap Input Password</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-lg-8 mt-5">
<form method="post" th:object="${user}">
<div class="form-group">
<label for="username">Username</label>
<div class="input-group">
<input type="text" class="form-control" id="username" autocomplete="off"
th:field="*{username}"/>
<div class="input-group-append">
<span class="input-group-text"><i class="fas fa-user-lock"></i></span>
</div>
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<div class="input-group" id="show_hide_password">
<input type="password" class="form-control" id="password" autocomplete="off"
th:field="*{password}"/>
<div class="input-group-append">
<span class="input-group-text"><i class="fa fa-eye-slash" aria-hidden="true"></i></span>
</div>
</div>
</div>
<button class="btn btn-primary" type="submit">Submit form</button>
</form>
</div>
</div>
</div>
<script th:src="@{/webjars/jquery/jquery.min.js}"></script>
<script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script>
<script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
<script th:src="@{assets/password-strength-meter/password.min.js}"></script>
<script>
$('#password').password({
closestSelector: '.form-group',
shortPass: 'The password is too short',
badPass: 'Weak; try combining letters & numbers',
goodPass: 'Medium; try using special characters',
strongPass: 'Strong password',
containsField: 'The password contains your username',
enterPass: 'Type your password',
showPercent: false,
showText: true, // shows the text tips
animate: true, // whether or not to animate the progress bar on input blur/focus
animateSpeed: 'fast', // the above animation speed
field: false, // select the match field (selector or jQuery instance) for better password checks
fieldPartialMatch: true, // whether to check for partials in field
minimumLength: 4 // minimum password length (below this threshold, the score is 0)
});
</script>
<script>
var input = $("#show_hide_password input");
var icon = $("#show_hide_password i");
icon.on('click', function (event) {
event.preventDefault();
if (input.attr("type") === "text") {
input.attr('type', 'password');
icon.addClass("fa-eye-slash");
icon.removeClass("fa-eye");
} else if (input.attr("type") === "password") {
input.attr('type', 'text');
icon.removeClass("fa-eye-slash");
icon.addClass("fa-eye");
}
});
</script>
</body>
</html>
To present password strength we used JavaScript library https://github.com/elboletaire/password-strength-meter created by Òscar Casajuana and available under the GPL 3.0 license. The library comes with many features, is easy to configure and allows us to present different text for various password irregularities.
The plugin initialization could look like the following:
Copy
$('#password').password({
closestSelector: '.form-group',
shortPass: 'The password is too short',
badPass: 'Weak; try combining letters & numbers',
goodPass: 'Medium; try using special characters',
strongPass: 'Strong password',
containsField: 'The password contains your username',
enterPass: 'Type your password',
showPercent: false,
showText: true, // shows the text tips
animate: true, // whether or not to animate the progress bar on input blur/focus
animateSpeed: 'fast', // the above animation speed
field: false, // select the match field (selector or jQuery instance) for better password checks
fieldPartialMatch: true, // whether to check for partials in field
minimumLength: 4 // minimum password length (below this threshold, the score is 0)
});
Additionally we created show/hide password option using a simple JavaScript code:
Copy
var input = $("#show_hide_password input");
var icon = $("#show_hide_password i");
icon.on('click', function (event) {
event.preventDefault();
if (input.attr("type") === "text") {
input.attr('type', 'password');
icon.addClass("fa-eye-slash");
icon.removeClass("fa-eye");
} else if (input.attr("type") === "password") {
input.attr('type', 'text');
icon.removeClass("fa-eye-slash");
icon.addClass("fa-eye");
}
});
When a user submits the form, the provided password will be encrypted using methods from Spring Boot Security .
The saved.html
simple present submitted values and has the following structure:
Copy
<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Spring Boot Thymeleaf Application - Bootstrap Input Password</title>
<link th:rel="stylesheet" th:href="@{webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "/>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container">
<a class="navbar-brand" href="/">Thymeleaf - Bootstrap Input Password</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-sm-12 mt-5">
<p>
<label>Username: </label><br/>
<strong th:text="${user.username}"></strong>
</p>
<p>
<label>Hashed password: </label><br/>
<strong th:text="${user.password}"></strong>
</p>
<a th:href="@{/}" class="btn btn-primary">Go back</a>
</div>
</div>
</div>
</body>
</html>
5. The output
The application is available under http://locahost:8080
URL and presents the following functionality:
6. Conclusion
In this article, we presented how to create Thymeleaf Input Password component with strength and show/hide option. We also used Spring Boot Security configuration for encrypting provided password. The application simply simulates user sign in to the system.
As usual, the code used in this article is available under our GitHub repository .
{{ 'Comments (%count%)' | trans {count:count} }}
{{ 'Comments are closed.' | trans }}