Friday 16 August 2019

Spring security: Encode password using BCrypt strong hashing function

This is continuation to my previous post. In previous post, we are storing user credentials such as password in plain text. Storing password as plain text leads to security risk. Let’s leverage the application using bcrypt. bcrypt is a password hashing function designed by Niels Provos and David Mazières, based on the Blowfish cipher, and presented at USENIX in 1999.

Spring has built-in support for bcrypt function.

Step 1: Create a bean of type 'BCryptPasswordEncoder'
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(16);
}

Step 2: Use passwordEncoder bean created in step 1 while extending WebSecurityConfigurerAdapter.
@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        return daoAuthenticationProvider;
    }

    .....
    .....
}


Step 3: Use password encoder while creating new user entities.
User user1 = new User();
user1.setUserName("krishna");
user1.setPassword(passwordEncoder().encode("password123"));

Total project structure looks like below.




Find the below working application.



User.java

package com.sample.app.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "user")
public class User {

    @Id
    @Column(name = "user_id")
    @GeneratedValue
    private int id;

    @Column(name = "user_name", nullable = false, unique = true)
    private String userName;

    @Column(name = "user_password", nullable = false)
    private String password;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}


UserPrincipal.java
package com.sample.app.model;

import java.util.Arrays;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class UserPrincipal implements UserDetails {

    private static final long serialVersionUID = 1L;
    private User user;

    public UserPrincipal(final User user) {
        super();
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        SimpleGrantedAuthority authority1 = new SimpleGrantedAuthority("USER");
        SimpleGrantedAuthority authority2 = new SimpleGrantedAuthority("ADMIN");

        return Arrays.asList(authority1, authority2);
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }


}


UserRepository.java
package com.sample.app.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.sample.app.model.User;

public interface UserRepository extends JpaRepository<User, Integer> {

    User findByUserName(String userName);
}


UserDetailsServiceImpl.java
package com.sample.app.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.sample.app.model.User;
import com.sample.app.model.UserPrincipal;
import com.sample.app.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService{
    
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUserName(username);
        
        if(user == null) {
            throw new UsernameNotFoundException("User not exist");
        }
        
        return new UserPrincipal(user);
    }

}


EmployeeController.java
package com.sample.app.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("employees/")
public class EmployeeController {

    @RequestMapping(value = "registered/count", method = RequestMethod.GET)
    public String countEmps() {
        return "Total Registered Employees : "+  1024;
    }
}


HelloWorldController.java
package com.sample.app.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
    @RequestMapping("/")
    public String homePage() {
        return "Welcome to Spring boot Application Development using Spring Security";
    }
    
    @RequestMapping("/public/aboutme")
    public String aboutMe() {
        return "I am securied by spring security module";
    }
    
}


ApplicationSecurityConfiguration.java
package com.sample.app.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.password.PasswordEncoder;

import com.sample.app.service.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfiguration extends WebSecurityConfigurerAdapter {

 @Autowired
 private UserDetailsServiceImpl userDetailsService;
 
 @Autowired
 private PasswordEncoder passwordEncoder;
 
 @Bean
 public DaoAuthenticationProvider authenticationProvider() {
  DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
  daoAuthenticationProvider.setUserDetailsService(userDetailsService);
  daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
  return daoAuthenticationProvider;
 }

 @Override
 protected void configure(HttpSecurity httpSecurity) throws Exception {
  httpSecurity.csrf().disable().authorizeRequests().antMatchers("/", "/public/*", "/css/*", "/js/*").permitAll()
    .anyRequest().authenticated().and().httpBasic();

 }
 
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception{
  auth.authenticationProvider(authenticationProvider());
 }

}


App.java
package com.sample.app;

import com.sample.app.model.User;

import java.util.Arrays;

import org.springframework.boot.CommandLineRunner;
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;

import com.sample.app.repository.UserRepository;

@SpringBootApplication
public class App {
    public static void main(String[] args) {

        SpringApplication.run(App.class, args);
    }

    @Bean
    public CommandLineRunner demo(UserRepository userRepository) {
        return (args) -> {

            User user1 = new User();
            user1.setUserName("krishna");
            user1.setPassword(passwordEncoder().encode("password123"));

            User user2 = new User();
            user2.setUserName("ram");
            user2.setPassword(passwordEncoder().encode("ram123"));

            userRepository.saveAll(Arrays.asList(user1, user2));
        };
    }

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


application.properties
logging.level.root=WARN
logging.level.org.hibernate=ERROR

## H2 specific properties
spring.h2.console.enabled=true
spring.h2.console.path=/h2

spring.datasource.url=jdbc:h2:file:~/db/myOrg.db;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;

spring.datasource.username=krishna
spring.datasource.password=password123

spring.datasource.driverClassName=org.h2.Driver

## JPA specific properties
# Creates the schema, destroying previous data.
spring.jpa.hibernate.ddl-auto=create

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

#spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true

## Database connection pooling properties
# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.max-wait=10000

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=10
spring.datasource.tomcat.max-idle=5
spring.datasource.tomcat.min-idle=3

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true


pom.xml
<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>
    <groupId>springSecurity</groupId>
    <artifactId>springSecurity</artifactId>
    <version>1</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <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-data-jpa</artifactId>
        </dependency>


        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>


    </dependencies>
</project>


Run App.java.

Open the url 'http://localhost:8080/employees/registered/count', you will be prompted with user credentials.  
  
Enter username as 'krishna' and password as 'password123' and click on OK button.    




You can download complete working application from this link.
   

Previous                                                    Next                                                    Home

No comments:

Post a Comment