Spring Security | Authentication | BCryptPasswordEncoder: Password Hashing

Why Hash Passwords?

If data is leaked, storing passwords as plain text is extremely dangerous. Passwords should generally be stored in a form that does not reveal the original string. When decryption is unnecessary, use a hash function.

BCrypt

MD5 and SHA are well-known hash functions, but BCrypt is convenient for password hashing.

Applying a hash function only once remains vulnerable to brute-force attacks, dictionary attacks, and rainbow tables.

BCrypt adds a random salt and hashes repeatedly, making the original password harder to guess. Spring Security also recommends BCrypt.

Implementation

Configure a PasswordEncoder on DaoAuthenticationProvider. To use BCrypt, use the BCryptPasswordEncoder implementation. Store precomputed BCrypt hash values.

XML Configuration

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       ...>

    ...

    <bean id="passwordEncoder"
          class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user
                    name="hoge"
                    password="$2a$08$CekzJRYhb5bzp5mx/eZmX.grG92fRXo267QVVyRs0IE.V.zeCIw8S"
                    authorities="ROLE_USER" />
            </sec:user-service>

            <sec:password-encoder ref="passwordEncoder" />
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>
  • Define BCryptPasswordEncoder as a Bean and set it in the ref attribute of <password-encoder>.
  • Configure <password-encoder> as a child element of <authentication-provider>.

Java Configuration

MySpringSecurityConfig.java

package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    ...

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth, PasswordEncoder passwordEncoder) throws Exception {
        auth.inMemoryAuthentication()
            .passwordEncoder(passwordEncoder)
            .withUser("hoge")
            .password("$2a$08$CekzJRYhb5bzp5mx/eZmX.grG92fRXo267QVVyRs0IE.V.zeCIw8S")
            .roles("USER");
    }
}
  • Specify the PasswordEncoder with the passwordEncoder() method of AbstractDaoAuthenticationConfigurer.
  • AbstractDaoAuthenticationConfigurer is the parent class of InMemoryUserDetailsManagerConfigurer, returned by AuthenticationManagerBuilder.inMemoryAuthentication().

Encoding Passwords

To encode a password, obtain PasswordEncoder from the container and use encode().

EncodePasswordServlet.java

package sample.spring.security.servlet;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/encode-password")
public class EncodePasswordServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());
        PasswordEncoder passwordEncoder = context.getBean(PasswordEncoder.class);

        String password = req.getParameter("password");
        String encode = passwordEncoder.encode(password);
        System.out.println(encode);
    }
}
  • The configuration is the same as described above: register BCryptPasswordEncoder as a Bean.
  • The example obtains PasswordEncoder explicitly for demonstration. In practice, inject it into a container-managed Bean.
  • Access http://localhost:8080/encode-password?password=fuga.

Server console output

$2a$10$2qVDkAqxp8eDrxR8Be2ZpubYGOCVZ7Qy9uK/XzOIY1ZoxpChtrWDK