Spring Boot + Bootstrap + Thymeleaf Input Password

February 01, 2020 No comments Spring Boot Bootstrap Thymeleaf Input Password Strength

Spring Boot + Bootstrap + Thymeleaf Input Password

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:

  • springframework - Spring Boot server, beans, controllers,
  • thymeleaf - Thymeleaf template engine,
  • lombok - setters/getters/constructor generator.
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:

<?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:

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:

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:

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()).


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:

<!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:

$('#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:

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:

<!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:

Thymeleaf bootstrap time picker

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.

{{ message }}

{{ 'Comments are closed.' | trans }}