Spring Security with Spring Boot 2.0: Password Encoder

On a previous post we used the user details service in order to provide a way to load our data from a function based on a username given.

The implementation of the user details might be backed by an in-memory mechanism, a sql/no-sql database etc.
The options are unlimited.

What we have to pay attention when it comes to password storage is the password hashing.
For security reasons we want to store passwords in a hashed form.
Supposing someone gets unauthorised access to the table storing our user data. By storing the passwords clear text that person can retrieve the password of every user in the system.

So we want a way to hash our passwords before storing them to database.
Always be aware that your hashing has to be robust and up to date.
For example MD5 was very popular in the past but nowadays leads to poor security. Actually it is possible to crack MD5 passwords fairly easy if you use a gpu.

Spring Security provides us with out of the box functionality when it comes to encoding passwords.
Password encoder is an interface which is used through the authorisation process.


package org.springframework.security.crypto.password;


public interface PasswordEncoder {

	String encode(CharSequence rawPassword);

	boolean matches(CharSequence rawPassword, String encodedPassword);

}

The encode function shall be used to encode your password and the matches function will check if your raw password matches the encoded password. Once your user details service fetches the user information from the database then the password given to authorise shall be validated with the one fetched from the database. In this case spring will use the matches function.

Now spring provides us with various implementations of a password encoder.
Let’s try to create a password encoder bean.

package com.gkatzioura.security.passwordencoder.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordEncoderConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                return rawPassword.toString();
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return rawPassword.toString().equals(encodedPassword);
            }
        };
    }
}

This bean is no different that the NoOpPasswordEncoder which comes with spring boot.
No we are going to do a small experiment and add a custom password encoder.
Our password encoder will compare the clear text password submitted by the user hash it and the compare it with an already hashed password from the equivalent user in our database.

To do the hashing we will user bcrypt.

    @Bean
    public PasswordEncoder customPasswordEncoder() {

        return new PasswordEncoder() {

            @Override
            public String encode(CharSequence rawPassword) {

                return BCrypt.hashpw(rawPassword.toString(), BCrypt.gensalt(4));
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {

                return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
            }
        };
    }

To test this we will set up our security by using the environment variables as we’ve seen on a previous post.

First we need to have our password encoded. Our system will not have the password stored in any clear text form.

System.out.println(BCrypt.hashpw("user-password",BCrypt.gensalt(4)));
$2a$04$i4UWtMw6surai4dQMhoKSeLddi1XlAh2sSyG58K3ZvBHqVkhz8Y3y

So what we are gonna do next is to set our environment variables before running our spring boot application.

SPRING_SECURITY_USER_NAME=test-user
SPRING_SECURITY_USER_PASSWORD=$2a$04$i4UWtMw6surai4dQMhoKSeLddi1XlAh2sSyG58K3ZvBHqVkhz8Y3y

Next step is to go to your login screen and give the credentials user-name and user-password.
As you can see you have just been authenticated.
Behind the scenes spring hashed the password you submitted and compared to the one existing through the environment varialbles.

Advertisement

Secure a docker registry using ssl

As mentioned on a previous article having a registry with a username and password is not secure if the registry is not ssl configured.

DockerImage

So we are going to add the ssl certificates to our registry. To make things easier we will use let’s encrypt which is free.

Once we have generated the credentials we have to add them to the registry. We will create a directory called certificates which will contain the certificate pem file and the key pem file. Then we will move the generated certificates on the certificates directory with the names crt.pem and key.crt.

We will follow exactly the same steps we followed in the previous article to generate the password.

docker run --entrypoint htpasswd registry:2 -Bbn {your-user} {your-password} > auth/password-file

Now we are ready to create our registry by also specifying the certificates. To do so we will mount the certificates directory to our docker container. The we will specify where the registry is going to find the credentials on the containers filesystem

docker run -d -p 5000:5000 --restart=always --name registry -v `pwd`/auth:/auth -v `pwd`/certificates:/certificates -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/password-file -e REGISTRY_HTTP_TLS_CERTIFICATE=/certificates/crt.pem -e REGISTRY_HTTP_TLS_KEY=/certificates/key.pem registry:2

So your registry will pickup the credentials specified and will also use the certificates created.
Next step is to do the dns mapping and add a dns entry which directs your subdomain to your registry’s ip.

However if you just wan’t to test it, you can run your registry locally and just change your /etc/hosts and add this entry.

127.0.0.1 registry.{your certificate's domain }

Once you navigate through your browser to https://registry.{your certificate’s domain }:5000
you will get a 200 status code and your browser will identify your connection as secure.