02 User Authentication How to Use Spring Security to Build User Authentication System

02 User Authentication How to Use Spring Security to Build User Authentication System #

In the previous lesson, we introduced the Spring Security framework and outlined its core functionalities. Starting today, we will delve into these functionalities one by one, starting with the user authentication functionality. User authentication involves building the user account system, which is a prerequisite for implementing authorization management. In Spring Security, there are multiple ways to implement user authentication, and we will discuss them in the context of the framework’s configuration system.

Spring Security Configuration System #

In Spring Security, because there are usually multiple ways to implement functionalities such as authentication and authorization, the framework has developed a complete configuration system to flexibly set up these functionalities. Developers rely on how to effectively utilize and extend this configuration system when using functionalities such as authentication and authorization.

For example, there are multiple strategies that can be designed for user account storage. We can store usernames and passwords in memory as a lightweight implementation. More commonly, we can store this authentication information in a relational database. Additionally, if we are using the LDAP protocol, the file system can also be a good storage medium.

Clearly, for these alternative implementation methods, a mechanism is needed to allow developers to flexibly configure them according to their own needs, and this is where the configuration system comes into play.

At the same time, you may have noticed that in the example from the previous lesson, Spring Security was able to function without any configuration, which indicates that the framework uses specific default configurations internally. For user authentication, Spring Security internally initializes a default username “user” and generates a password at application startup. However, passwords generated in this way change each time the application starts and are not suitable for production applications.

We can further understand some default configurations in Spring Security by examining the framework’s source code (https://github.com/spring-projects/spring-security). In Spring Security, the configuration class on which the initialization of user information depends is the WebSecurityConfigurer interface, which is actually an empty interface that extends the more fundamental SecurityConfigurer interface.

In daily development, we usually don’t need to implement this interface ourselves, but rather use the WebSecurityConfigurerAdapter class to simplify the usage of this configuration class. In the WebSecurityConfigurerAdapter class, we find the configure method shown below:

protected void configure(HttpSecurity http) throws Exception {
    
    http
        .authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin().and()
        .httpBasic();
}

The above code is the default implementation in Spring Security for user authentication and access authorization. It uses multiple common configuration methods. Once we have imported the Spring Security framework into the classpath, as we mentioned in the previous lesson, a login page will pop up when accessing any endpoint to complete user authentication. Authentication is the prerequisite process for authorization.

Let’s analyze how this default effect is achieved based on these configuration methods:

  • First, the authorizeRequests() method of HttpSecurity class restricts access to all HTTP endpoints of HttpServletRequest.
  • Then, the anyRequest().authenticated() statement specifies that authentication needs to be performed for all requests, meaning that users who have not been authenticated cannot access any endpoint.
  • Next, the formLogin() statement specifies the use of form-based login as the authentication method, which means that a login page will pop up.
  • Finally, the httpBasic() statement indicates that HTTP Basic Authentication can be used to perform authentication.

In the process of daily development, we can inherit the WebSecurityConfigurerAdapter class and override the above configure() method to complete the configuration work. In Spring Security, there are a group of configuration classes similar to WebSecurityConfigurerAdapter.

The configuration system is one of the main means by which developers use the Spring Security framework, and discussions on the configuration system will be continuous throughout this column. As the content deepens, the comprehensive and flexible configuration features provided by Spring Security will be presented to you one by one.

Implementing HTTP Basic Authentication and Form-Based Login Authentication #

In the previous text, we mentioned httpBasic() and formLogin(), which represent the two means of controlling user authentication, namely HTTP Basic Authentication and form-based login authentication. When building a web application, we can also extend the authentication mechanism provided by Spring Security to meet daily development needs.

HTTP Basic Authentication #

The principle of HTTP Basic Authentication is relatively simple. It only requires sending the username and password in the HTTP protocol’s message header for login verification. In the previous lesson, we performed a simple user login operation through the browser. Today, we will use Postman, a visual HTTP request tool, to further analyze the request and response process of the login.

In Postman, when we directly access the http://localhost:8080/hello endpoint, we get the following response:

{
    "timestamp": "2021-02-08T03:45:21.512+00:00",
    "status": 401,
    "error":"Unauthorized",
    "message":"",
    "path":"/hello"
}

Clearly, the response code 401 tells us that we do not have permission to access this address. At the same time, a “WWW-Authenticate” message header appears in the response, with a value of “Basic realm=“Realm””. Here, Realm represents the security domain of protected resources in the web server.

Now, let’s perform HTTP Basic Authentication. You can complete the access to the HTTP endpoint by setting the authentication type to “Basic Auth” and entering the corresponding username and password. The setting interface is as follows:

Drawing 0.png

Setting HTTP Basic Authentication using Postman

Now, after checking the HTTP request, you can see that the Authorization header is added to the Request Header with the format: Authorization:<type> <credentials>. Here, the type is “Basic”, and the credentials is a string like this:

dXNlcjo5YjE5MWMwNC1lNWMzLTQ0YzctOGE3ZS0yNWNkMjY3MmVmMzk=

This string is the result obtained by combining the username and password together and then being Base64 encoded. And we know that Base64 is just an encoding method and does not integrate encryption mechanisms, so the essence of the transmission is still in plaintext form.

Of course, enabling HTTP Basic Authentication in an application is relatively simple. Just add the following configuration to the configure method of WebSecurityConfigurerAdapter:

protected void configure(HttpSecurity http) throws Exception {
  http.httpBasic();
}

HTTP Basic Authentication is relatively simple and does not have a customized login page, so its standalone usage is limited. When using Spring Security, we generally combine HTTP Basic Authentication with the subsequent form-based authentication.

Form-Based Authentication #

In the configure() method of WebSecurityConfigurerAdapter, once the formLogin() method of HttpSecurity is configured, form-based authentication is enabled, as shown below:

protected void configure(HttpSecurity http) throws Exception {
  http.formLogin();
}

The effect of the formLogin() method is to provide a default login page, as shown below:

Drawing 1.png

Default login page of Spring Security

We have seen this login page in the previous lecture. For logins, this login page is usually customized, and we also need to have fine control over the login process and results. At this time, we can modify the default configuration of the system through the following configuration:

@Override
protected void configure(HttpSecurity http) throws Exception {
  http
    // ... other configurations
    .and()
    .formLogin()
      .loginPage("/login") // specify the login page URL
      .loginProcessingUrl("/authenticate") // specify the processing URL of the login form
      .successHandler(mySuccessHandler) // specify a custom success handler
      .failureHandler(myFailureHandler) // specify a custom failure handler
    // ... other configurations
    ;
}
            .formLogin()        
    
            .loginPage("/login.html")//Customize the login page
    
            .loginProcessingUrl("/action")//The processing URL when the login form is submitted
    
            .defaultSuccessUrl("/index");//The default URL to redirect to after successful login        
    
    }
    

As you can see, here we have customized the login page, the processing URL for the login form, and the URL to redirect to after successful login.

### Configure the Spring Security User Authentication System

After discussing configuration, let's now move back to the user authentication scenario. The default username provided by Spring Security is fixed, while the password changes with each application startup, which is not very flexible. In Spring Boot, we can change this default behavior by adding the following configuration in the application.yml file:
    
    
    spring:
    
      security:
    
        user:
    
          name: spring
    
          password: spring_password
    

Now let's restart the application and use the above username and password to complete the login. Storing user information in a configuration file is simple and straightforward, but obviously lacks flexibility because we cannot dynamically load the corresponding username and password during runtime. Therefore, in reality, we mainly use the WebSecurityConfigurerAdapter configuration class to change the default configuration behavior.

Based on the previous content, we already know that we can use the configure(HttpSecurity http) method of the WebSecurityConfigurerAdapter class to perform authentication. The authentication process involves the interaction of user information in Spring Security, and we can configure user information by inheriting the WebSecurityConfigurerAdapter class and overriding the configure(AuthenticationManagerBuilder auth) method. Please note that these are **two different configure() methods**.

For the WebSecurityConfigurer configuration class, we first need to clarify what needs to be configured. In fact, initializing user information is very simple, we just need to specify three data: username, password, and role. In Spring Security, the AuthenticationManagerBuilder class provides developers with multiple validation schemes based on memory, JDBC, LDAP, etc.

Next, let's implement multiple user information storage schemes around the functionalities provided by AuthenticationManagerBuilder.

#### Use In-Memory User Information Storage Scheme

Let's first see how to use AuthenticationManagerBuilder to achieve an in-memory user information storage scheme. The implementation method is to call the inMemoryAuthentication method of AuthenticationManagerBuilder, as shown in the example code below:
    
    
    @Override
    
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
    
     
    
        builder.inMemoryAuthentication()
    
            .withUser("spring_user").password("password1").roles("USER")
    
            .and()
    
            .withUser("spring_admin").password("password2").roles("USER", "ADMIN");
    
    }
    

From the above code, we can see that there are two users in the system, namely "spring_user" and "spring_admin", with passwords "password1" and "password2" respectively. They also have roles as a regular user and an administrator.

Please note that the roles() method actually uses the **authorities() method** behind the scenes. With the roles() method, Spring Security automatically adds the "ROLE_" prefix to each role name. Therefore, we can achieve the same functionality with the following code:
    
    
    @Override
    
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
    
     
    
        builder.inMemoryAuthentication()
    
             .withUser("spring_user").password("password1").authorities("ROLE_USER")
    
             .and()
    
             .withUser("spring_admin").password("password2").authorities("ROLE_USER", "ROLE_ADMIN");
    
    }
    

It can be seen that the implementation of the in-memory user information storage scheme is also relatively simple, but it lacks flexibility because the user information is hard-coded in the code. Therefore, we will introduce another more common user information storage scheme--database storage.

#### Use Database User Information Storage Scheme

Since we are storing user information in a database, table structures need to be **created**. We can find the corresponding SQL statements in the source file of Spring Security (org/springframework/security/core/userdetails/jdbc/users.ddl), as shown below:
    
    
    create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);
    
     
    
    create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
    
     
    
    create unique index ix_auth_username on authorities (username,authority);
    

Once we have created these two tables in our own database and added the corresponding data, we can directly query user data using an injected DataSource object, as shown below:
    
    
    @Autowired
    
    DataSource dataSource;
    
     
    
    @Override
    
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
     
    
            auth.jdbcAuthentication().dataSource(dataSource)
    
                   .usersByUsernameQuery("select username, password, enabled from Users " + "where username=?")
    
                   .authoritiesByUsernameQuery("select username, authority from UserAuthorities " + "where username=?")
    
                   .passwordEncoder(new BCryptPasswordEncoder());
    
    }
    

Here, we use the jdbcAuthentication method of AuthenticationManagerBuilder to configure the database authentication method, which internally uses the JdbcUserDetailsManager utility class. In this class, various SQL statements for database queries are defined, as well as the specific implementation methods for accessing the database using JdbcTemplate.

Please note that we used a **passwordEncoder() method** here, which is a password encoder/decoder provided by Spring Security. We will discuss this in detail in the lecture on "Password Security: What encryption and decryption technologies does Spring Security include?".

### Summary and Preview

In this lecture, we have detailed how to use Spring Security to build a user authentication system. In Spring Security, authentication-related functionalities can be customized and managed through configuration. With simple configuration methods, we can combine HTTP basic authentication and form login authentication, and store user information in memory or in a database, which are all built-in functions of Spring Security.

The summary of this lecture is as follows:

![Drawing 2.png](../images/CioPOWC5_g-AP68cAACSqF1r51g526.png)

Finally, I would like to leave you with a question: Do you know which user information storage implementation schemes are available in Spring Security? Please feel free to share your thoughts in the comments.