Spring Security Basics: Implementing Authentication and Authorization-PART 4
Published: 2024-06-06 23:55:00

## Integrate the database with Spring Security

Up to this point, we have used the default user provided by Spring Security to log in to the application. In the previous sections, we added some sample users to the `application_users` table. Moving forward, we will use this table for logging in to the application. To achieve this, we need to configure Spring Security to recognize that the details of the application users are stored in the `application_users` table. This will enable Spring Security to retrieve and verify user credentials during authentication and authorization.

For that let's do the following steps:

1. Add the password encoder bean
    
2. Update the plain text password to encrypted password
    
3. Configure user details service
    
4. Configure the authentication provider
    

### Add the password encoder bean

In the `SecurityConfig` class add a bean of type `PasswordEncoder`

```java
package com.gintophilip.springauth.web;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity securityConfig) throws Exception {
        return securityConfig
                .authorizeHttpRequests(auth->
                        auth.requestMatchers("/api/hello")
                                .permitAll()
                                .requestMatchers("/api/admin").hasRole("ADMIN")
                                .anyRequest().authenticated()
                ).formLogin(Customizer.withDefaults())
                .build();
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
```

The `BCryptPasswordEncoder` is the preferred method for encoding passwords.

### Update the plain text password to encrypted password.

While creating the sample users, the passwords were stored as plain text. In this step, let's update the passwords to an encrypted form.
>You may wonder why user creation is handled in this manner and not through an API. The reason is, to minimize the overhead of creating APIs for every function. The primary goal of this article is to demonstrate the fundamentals of integrating authentication and authorization mechanisms.

1. Drop the `user_roles` table
    
2. Drop the `roles` table
    
3. Drop the `application_user` table
    
4. Add the `PasswordEncoder` class dependency in `DataBaseUtilityRunner`
    
5. Encode the password
    

**Drop the**`user_roles`**table**

```bash
spring_access_db=# drop table user_roles;
```

**Drop the**`roles`**table**

```bash
spring_access_db=# drop table roles;
```

**Drop the**`application_user`**table**

```bash
spring_access_db=# drop table application_user;
```

>The drop tables command were executed via psql CLI

And also, do the following in `DataBaseUtilityRunner` class.

*update the lines*

```java
user1.setPassword("123456");
adminUser.setPassword("12345");
```

as follows.

```java
 user1.setPassword(passwordEncoder.encode("123456"));
 adminUser.setPassword(passwordEncoder.encode("12345"));
```

The `DataBaseUtilityRunner` after modification is as follows,

```java
package com.gintophilip.springauth;

import com.gintophilip.springauth.entities.ApplicationUser;
import com.gintophilip.springauth.entities.Roles;
import com.gintophilip.springauth.repository.RoleRepository;
import com.gintophilip.springauth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class DataBaseUtilityRunner implements CommandLineRunner {
    @Autowired
    PasswordEncoder passwordEncoder;
    @Autowired
    UserRepository usersRepository;
    @Autowired
    RoleRepository rolesRepository;
    @Override
    public void run(String... args) throws Exception {
        try {
            Roles userRole = new Roles();
            userRole.setRoleName("USER");
            Roles adminRole = new Roles();
            adminRole.setRoleName("ADMIN");
            rolesRepository.save(userRole);
            rolesRepository.save(adminRole);
            ApplicationUser user1 = new ApplicationUser();
            user1.setFirstName("John");
            user1.setEmail("john@test.com");
            user1.setPassword(passwordEncoder.encode("123456"));
            user1.setRole(userRole);

            ApplicationUser adminUser = new ApplicationUser();
            adminUser.setFirstName("sam");
            adminUser.setEmail("sam@test.com");
            adminUser.setPassword(passwordEncoder.encode("12345"));

            adminUser.setRole(adminRole);
            usersRepository.save(user1);
            usersRepository.save(adminUser);
        }catch (Exception exception){

        }

    }
}
```

Run the application and query the `application_user` table. You will see the passwords are encoded now instead of plain text.

```bash
spring_access_db=# select * from application_user;
 id  |     email     | first_name | last_name |            password                           
-----+---------------+------------+-----------+--------------------------------------------------------------
 102 | john@test.com | John       |           | $2a$10$IBP9g8pOvNCvkEc7/EG6TO3j6gh49QMuO6uuw9Dd/P9dPRi5mxbiAsnG
 103 | sam@test.com  | sam        |           | $2a$10$WxM0l4qnjbYn1Vkgmrbte.hgYqPyHLm/y.9IGvEoiRkrL8.h47QKu
(2 rows)
```

### Configure user details service

For making spring security to use the database for authentication and authorization a user details service needs to be implemented. This is nothing but a class which implements the interface `UserDetailsService` .

This interface has a method named `loadUserByUsername` which accepts a string parameter and returns a `UserDetails` object.

```java
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
```

Create a class named `DatabaseUserDetailsService.java` which implements the `UserDetailsService` interface.

```java
public class DatabaseUserDetailsService  implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
       return null;
    }
}
```

In the overridden method we do the following

1. Fetch the user from database based on the parameter username.
    
2. Retrieve the roles associated with the user
    
3. Create an instance of class `User` with the user details and the associated roles
    
4. Return the `User` object
    

```java
package com.gintophilip.springauth.service;

import com.gintophilip.springauth.entities.ApplicationUser;
import com.gintophilip.springauth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 java.util.Collections;

@Service
public class DatabaseUserDetailsService  implements UserDetailsService {
    @Autowired
    UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        ApplicationUser user = userRepository.findByEmail(username); //fetch user from db
        if(user == null){
            throw  new UsernameNotFoundException("User Not found");
        }
        //Retrieve user roles
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+user.getRole().getRoleName());
        //create User object
        User applicationUser = new User(user.getEmail(),user.getPassword(), Collections.singleton(authority));
        return applicationUser;
    }
}
```

>The <code>User</code> class implements the <code>UserDetails</code> interface. Spring utilizes the <code>UserDetails</code> to generate an Authentication object. This Authentication object indicates whether the user is authenticated.

Next we need to configure an authentication provider.

### Configure the authentication provider

For Spring Security to utilize the `DatabaseUserDetailsService` for retrieving user details, it must be linked with an authentication provider. For that we will create a bean of type `DaoAuthenticationProvider` in the `SecurityConfig` and bind it with `DatabaseUserDetailsService` within the `SecurityConfig`.

Bind the `DatabaseUserDetailsService`

```java
    @Autowired
    DatabaseUserDetailsService databaseUserDetailsService;
```

Next, create an instance of `DaoAuthenticationProvider` which is an implementation of `AUthenticationProvider` given by Spring security. Then,

1. Link the `databaseUserDetailsService`
    
2. Link the `passwordEncoder`
    

```java
 @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        //set the user details object        
        authenticationProvider.setUserDetailsService(databaseUserDetailsService);
        //set the password encoder        
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }
```

After this the `SecurityConfig` looks like below.

```java
package com.gintophilip.springauth.web;

import com.gintophilip.springauth.service.DatabaseUserDetailsService;
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.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    DatabaseUserDetailsService databaseUserDetailsService;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity securityConfig) throws Exception {
        return securityConfig
                .authorizeHttpRequests(auth->
                        auth.requestMatchers("/api/hello")
                                .permitAll()
                                .requestMatchers("/api/admin").hasRole("ADMIN")
                                .anyRequest().authenticated()
                ).formLogin(Customizer.withDefaults())
                .build();
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        //set the user details object
        authenticationProvider.setUserDetailsService(databaseUserDetailsService);
        //set the password encoder
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }
}
```

Next, run the application and access the APIs. When the login page appears, use the user details from the `application_user` table.

1. [http://localhost:8080/api/hello](http://localhost:8080/api/hello)
    
2. [http://localhost:8080/api/protected](http://localhost:8080/api/protected)
    
3. [http://localhost:8080/api/admin](http://localhost:8080/api/admin)
    

You will be successfully authenticated and able to view the response.

The `/api/admin` is only accessible to the user ***sam***.




---