DEV Community

Cover image for Spring Security: Configuring UserDetailsService (Part 1)
Ramachandra Petla
Ramachandra Petla

Posted on

Spring Security: Configuring UserDetailsService (Part 1)

When we create an API for providing services or an web application, it is crucial to provide authentication and authorization to the project. In this post I am going to tell how to use Spring Security module to make the necessary security configurations.

To start, I am going to create a RestController with two endpoints "/", "/welcome" and try to run the application and see what happens.

@RestController
public class HomeController {   
   @GetMapping("/")
   public String sayHello() {
      return "Welcome to Spring Security!";
   }
   @GetMapping("/welcome")
   public String sayHello() {
      return "Welcome to Spring Security(Protected)!";
   }

}
Enter fullscreen mode Exit fullscreen mode

Here is the result !

base endpoint without spring security module
Lets see what happens if we add spring security to the project dependency

<dependency>             
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Now if we run the application and try to hit and endpoint you will be redirected to a login page which will look like below.

Endpoint after adding spring security
Well, I don't remember adding a /login endpoint nor configured any users. But how the login page rendered and why?
Spring security is enabled just by adding the dependency to the classpath. By default all the routes/endpoints needs authentication to access. But what credentials are used to login? I don't remember configuring any credentials!. It is because if we didn't configure it spring will provide a default random credentials at run time

auto generated password

Security Configuration

Spring MVC sends all incoming HTTP requests into a single servlet named DispatcherServlet after passing through set of filters. The DispatcherServlet is responsible to send the HTTP request to the corresponding Controller.
When we add Spring Security to the classpath a set of Security Filters are added to the FilterChain. These filters intercept every HTTP request before they come to the Controllers. This is where Spring security authenticates the HTTP requests and decides either to forward or reject the request.
Spring Security Architecture

By default, every HTTP request is authenticated. But, we can override this behaviour by making our own security configuration

In order to do that, we need to provide our custom SecurityFilterChain bean

Default behaviour is due to the bean

@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests().anyRequests().authenticated();
    http.formLogin();
    http.httpBasic();
    return http.build();
}
Enter fullscreen mode Exit fullscreen mode

Now, we can have our own bean in a brand new configuration class like this.

@Configuration
class ApiSecurityConfiguration {    
   @Bean
   SecurityFilterChain customSecurityFilterChain(HttpSecurity http) throws Exception {
      http.authorizeHttpRequests((requests)-> requests
           .requestMatchers("/welcome").authenticated()              
               .anyRequests().authenticated());
       .formLogin();
       .httpBasic(Customizer.withDefaults());
      return http.build();
   }
}
Enter fullscreen mode Exit fullscreen mode

The above configuration makes /welcome endpoint is authenticated and any endpoint other than that are publicly accessible

But we still have not set up our own set of users.
Before moving forward with Users setup, we need to understand the how the request is processed.

UserDetailsService

UserDetailsService (Interface) -> loadUserByUserName(String username)
This is the Core Interface which loads user specific data

UserDetailsManager(Interface extends UserDetailsService) Extension of userDetailsService which allows to create, update, delete UserDetails (see method signatures below)

  • createUser(UserDetails user)
  • updateUser(UserDetails user)
  • deleteUser(String username user)
  • changePassword(String oldPwd, String newPwd)
  • userExists(String username)

Sample Implementation classes ( for UserDetailsManager interface ) provided by Spring Security are InMemoryUserDetailsManager, JdbcUserDetailsManager, LdapUserDetailsManager

All the above interfaces and classes use an interface UserDetails which provides core user information

For proving user details for authetication, we need to implement UserDetailsService interface and provide it as a spring bean. We can either use the default implementations provided by spring or we can have our own custom implementation.
If we are building enterprise application, it is recommended to have your own custom implementation. But for simplicity, we will use the default implementations provided
In this post, we will see how we can configure InMemoryUserDetailsManager

@Bean
UserDetailsService userDetailsService() {
   UserDetails admin = User.withDefaultPasswordEncoder()                         
                           .username("admin")                                
                           .password("12345")                                
                           .authorities("admin")                                 
                           .build();    
   UserDetails user = User.withDefaultPasswordEncoder()
               .username("user")
               .password("54321")
               .authorities("read")
               .build();
   return new InMemoryUserDetailsManager(user, admin);
    }
Enter fullscreen mode Exit fullscreen mode

Here, we are using DefaultPasswordEncoder(). But, in general we need to provide a PasswordEncoder bean. Spring provides us with many password encoders
For Example,

@Bean
PasswordEncoder passwordEncoder() {
   return new BCryptyPasswordEncoder()
}
Enter fullscreen mode Exit fullscreen mode

Now, with these configurations, we can access the protected routes using the provided users in InMemory.
We can also get users from database using default JdbcUserDetailsManager bean offered by spring Security
In order to use this we need to have a datasource
We are going to use mysql for this application. Datasource configurations are done in applications.properties file or we have application.yml

spring:
  datasource:
    platform: mysql
    url: jdbc:mysql://localhost:5432/mydb
    username: foo
    password: bar
    jpa:
       show-sql: true
       properties:
          hibernate:
        format_sql: true
Enter fullscreen mode Exit fullscreen mode

By Default JdbcUserDetailsManager default implementation, assumes we follow a database schema which has users(id, username, password, enabled) and authorities(id, username, authority) tables
So, if we are using JdbcUserDetailsManager implementation we need to follow the same ddl schema
Replace InMemoryUserDetailsManager bean with JdbcUserDetailsManager bean (Make sure you pass DataSoucrce object)

@Bean
UserDetailsService userDetailsService(DataSource dataSource) {
   return new JdbcUserDetailsManager(dataSource)
}
Enter fullscreen mode Exit fullscreen mode

Well, we have configured our application to use InMemory UserDetails and configured to fetch user details from database as well using default implementations.

But this is not enough for enterprise application, since we might not follow the same naming convention in the database based on client recommendation. In that case we cannot use JdbcUserDetailsManager.

But that is a topic for another day.
I will write more about it in my next post.

Top comments (0)