In this blog, we will demonstrate how to add basic authentication your your Spring Boot application.

We will start with the simplest possible authentication using in-memory user authentication, and then move to authentication using users/roles from standard tables for auth.

Finally we will move onto authentication using custom user and roles tables, and also look at Spring’s internal classes/workflow that does the actual authentication.

Table of Contents

The entire code of for this project can be found at https://github.com/chatterjeesunit/spring-boot-app/tree/v3.0

To checkout this Release tag, run following command

git clone https://github.com/chatterjeesunit/spring-boot-app.git
cd spring-boot-app
git checkout tags/v3.0 -b v3.0-with-auth

1.0 Adding Simple In-Memory User Authentication

In our previous blogs, we demonstrated how to create Spring Boot application and add Flyway

However the application lacked a very basic feature – Application Security. There was no authentication and authorization build into the application. Anybody could hit the application urls and create and fetch data.

Authentication is verifying your identity. e.g. Login in via your username and password.

Authorization is being able to verify what you can access. e.g. Accessing pages/ links etc, based on your roles and privileges

This blog builds on top of the previous blog for Adding Flyway Integration to Spring Boot Application.

Hence the source code for the base Spring Boot application that we will use can be found at – https://github.com/chatterjeesunit/spring-boot-app/tree/v2.0

We recommend that you follow this step by step guide using the above base source code. But it is not required, and you can follow these steps for any Spring boot application code that you have and add authentication to it.

1.1 Configurations

Add following Spring Security dependency to your gradle project.

compile('org.springframework.boot:spring-boot-starter-security')

1.2 Adding Authentication

Create new Security Configuration Class

  • This class should extend from org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
  • Add annotation @Configuration to the class

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{

}

Now override the method – configure(HttpSecurity http) in this class, and add following code to enable authentication.


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

The above code configures HTTP security to the application.
This line of code is most important – .authorizeRequests().anyRequest().authenticated() – as it tells that all HTTP Requests will be authenticated.

Now try hitting any of the application url

curl -i -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'

You will get a HTTP 401 status back, which mean UnAuthorised Request.
Below is the sample response that you may get

HTTP/1.1 401
Set-Cookie: JSESSIONID=20BDADF36A01A509D6E5D327A6FBC6CB; Path=/; HttpOnly
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 15 Aug 2019 13:23:15 GMT

{"timestamp":"2019-08-15T13:23:15.455+0000","status":401,"error":"Unauthorized","message":"Unauthorized","path":"/customer/"}%

This means that we have added authentication to our application and nobody can access the application REST urls without providing authentication information.

1.3 Adding In Memory User Authentication

As of now there are no users in the system that we can authenticate with.
To start with lets create an in memory database of users that we use for authentication.

Using In Memory authentication is useful for demo purposes but not good for production environments. In later sections we will later see how to do authentication with users in database.

Modify the security config class that we created earlier – SecurityConfig and add following

  • Override following method – configure(AuthenticationManagerBuilder auth)
  • Lets add two users for authentication purpose
    • User: admin01 , Password : admin01@123# , Role : ADMIN
    • User: user01, Password : welcome@123#, Role : USER

Although we have added User Roles, ignore them for now, and we will use them later.


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .inMemoryAuthentication()
        .withUser("admin01")
        .password("{noop}admin01@123#")
        .roles("ADMIN")
        .and()
        .withUser("user01")
        .password("{noop}welcome@123#")
        .roles("USER");
}

We have prefixed {noop} in front of all the passwords, to ensure that the Spring Boot application uses the following password encoder – org.springframework.security.crypto.password.NoOpPasswordEncoder.

This is NOT a secure option but good to start with, as it performs authentication using un-encrypted text passwords.

In later sections we will see how to use encrypted passwords.

1.4 Testing Authentication

Build and run the application. Now we can authenticate http requests to the application using the above two users that we created in in memory database.

We will do testing using two REST clients – curl and Postman

1.4.1 Testing using CURL

Use the -u <user>:<password> option of curl command to provide the username and password for the http request.
e.g Just add -u admin01:admin01@123# OR -u user01:welcome@123# to the http requests.

curl -i -u user01:welcome@123#  -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'

Now you can see the the http requests are getting authenticated and are successful.

You can also try with some invalid username or password combination, to test the 401 Unauthorised response.

1.4.2 Testing using Postman

If you are using Postman Rest client, then select BasicAuth under Authorization tab, and provide the username and password.

1.5 Skipping Authentication for Some Urls

There are situations where you may not want authentication for all urls of the system. Some example could be application health and metric urls, that should be available without authentication.
Try running below Spring Boot Health url, and you will get 401 Unauthorized error

curl -i 'localhost:8080/actuator/'
curl -i 'localhost:8080/actuator/health'

So we may need to add configuration so skip authentication for some urls.

To do this modify the Configure HTTP Auth method in the SecurityConfig class, and allow these URLs explicitly.
e.g Add following line of code – .antMatchers("/actuator/**").permitAll()


@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .formLogin().disable()
        .csrf().disable()
        .httpBasic()
        .and()
        .authorizeRequests()
        .antMatchers("/actuator/**").permitAll()
        .anyRequest().authenticated();
}

Above code will ensure that any URL that starts with /actuator/ will not be authenticated, and remaining all http requests will require authentication.

Top ∆

2.0 Adding Basic Role Based Authorization

Now that we have added authentication, lets look into the authorization aspect. As of now any authenticated users can access all http requests of the application.

Ideally this is never the real world scenario and we have people given access to specific areas of the system.

Lets consider a scenario, where we want that

  • Only users with ADMIN role can create or update customers
  • But all users can find customers or get customers.

So basically what we want is to that only users with ADMIN role can do POST or PUT http requests, and any other authenticate users can do GET requests

To do this we will add another AntMatcher in the HTTP Security Config and provide role information that is required for it.


@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .formLogin().disable()
        .csrf().disable()
        .httpBasic()
        .and()
        .authorizeRequests()
        .antMatchers("/actuator/**").permitAll()
        .antMatchers(HttpMethod.POST).hasRole("ADMIN")
        .antMatchers(HttpMethod.PUT).hasRole("ADMIN")
        .anyRequest().authenticated();
}

Now lets try hitting the POST Request with user01, who does not have ADMIN role access. You should get HTTP 403 status back on doing this.
This means the user is authenticated but not authorized or forbidden to access this request.

curl -i -u user01:welcome@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'

HTTP/1.1 403
.....
.....
{"timestamp":"2019-08-15T15:19:32.082+0000","status":403,"error":"Forbidden","message":"Forbidden","path":"/customer/"}%

However when you try the same request with admin01@tw.com user, the request will get authorized.

curl -i -u admin01:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'

But you can make the GET requests with users of any role.


curl -i -u user01:welcome@123# 'localhost:8080/customer/?pageNum=0&pageSize=2'

curl -i -u admin01:admin01@123# 'localhost:8080/customer/?pageNum=0&pageSize=2'

Top ∆

3.0 Authenticating using database user/roles using encrypted passwords

Till now we have added both authentication and authorization to our application, and also skipped authentication for some urls like Spring Boot Actuator urls.

So far so good.

However, this application is still not ready for production. Two primary reasons

  • User authentication should be done using Users in the database
  • Passwords should be encrypted.

Lets do both of them in this section

3.1 Database changes

We will need two tables in the database

  • users – for storing the user information
  • authorities – for user-role definitions

The advantage of using the default table names/table schema is that you don’t need to add any Java code to fetch users, etc. The Default implementation of Spring Boot automatically takes care of it.

Below script will create the required tables and also create initial data of users and roles that we require for our authentication demo.

If you are using Flyway, then add below SQL to flyway migration script.
Else you can just run the below script on your database.


create table users(
	username varchar(50) not null primary key,
	password varchar(500) not null,
	enabled boolean not null);

create table authorities (
	username varchar(50) not null,
	authority varchar(50) not null,
	constraint fk_authorities_users foreign key(username) references users(username));

create unique index ix_auth_username on authorities (username,authority);


INSERT INTO `users` (`username`, `password`, `enabled`)
VALUES ('admin01', '{noop}admin01@123#', true);

INSERT INTO `users` (`username`, `password`, `enabled`)
VALUES ('user01', '{noop}welcome@123#', true);


INSERT INTO `authorities` (`username`, `authority`)
VALUES ('admin01', 'ROLE_ADMIN');

INSERT INTO `authorities` (`username`, `authority`)
VALUES ('user01', 'ROLE_USER');

commit;

Make sure to prefix ROLE_ to the name of the roles when you insert into the authorities database.

3.2 Enabling authentication using database users

Now our database is ready with all users and role information.

Modify SecurityConfig class, and change the code in configure(AuthenticationManagerBuilder auth) method.

  • Remove the entire code for In memory database
  • Add code for JDBC authentication

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

Next autowire the Datasource into your SecurityConfig class

@Autowired
DataSource dataSource;

Add a Bean Definition for PasswordEncoder


@Bean
public PasswordEncoder getPasswordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

Test the application again


curl -i -u admin01:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'

curl -i -u user01:welcome@123# 'localhost:8080/customer/?pageNum=0&pageSize=2'

3.3 Using encrypted Passwords

Now lets perform the last step and encrypt the passwords.

We will use Bcrypt Hashing to generate encrypted passwords. We will create the encrypted passwords using Online Bcyrpt password generator.

Now lets modify the database and update the encrypted passwords. Either apply the script through Flyway migration script or apply it directly on your database.


UPDATE `users`
set `password` = '$2a$10$4LEwPTJ86OF/oZUn8hl0vOhSUhFqX5YwNO./i/bTeTD6cn5lRLj2S'
where `username` = 'admin01';


UPDATE `users`
set `password` = '$2a$10$yPIWEiYj8sGLox.9cPKPZe6GgGRy.T8iV/sR2Br1PyA0UzLaYVOa.'
where `username` = 'user01';

commit;

Next modify the SecurityConfig code to use BcryptPasswordEncoder.

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

Run the application and test again.
Now the authentication and authorization is working from JDBC authentication and using encrypted passwords.

Top ∆

4.0 Using custom user/roles tables for authentication

3.4 Authentication using custom database tables for users and roles

So far we have seen how to JDBC based authentication using default database schema for users and authorities table.

What if you already have a different table for Users and roles in your database and it does not match with the recommended schema for users and authorities?

What if we already have a different table with all user data and we want to the users to login using their email_address?

Spring boot is very flexible in this matter and also allows us to customize the authentication from our custom tables, as long as we have some way to identify users and their passwords and associated roles.

4.1 Custom tables for users and roles

Suppose your application already has following user and roles tables as given below

You may want to use your custom tables for user authentication and user user’s email address as username for authentication.

This assumes you already have these tables and users and roles data inserted into the tables, and that the user_info table contains passwords encrypted using Bcrypt Hashing.

However if you don’t have these tables but still want to try them out, you can run this script to create the tables and insert users.

4.2 Modifying Security Configuration to use custom database tables

In order to authenticate using user and role information from custom tables, we need to create two queries first.

  • User Query : We need a database query to fetch users by user’s login name, and return it’s username, password and a boolean flag (enabled or not). Since we don’t have enabled flag in our database schema, we return a default value of true.

select email_address, password, true as enabled 
from user_info where email_address = ?
  • User Role Query : We need a database query to get username and the name of role associated with the user.

select u.email_address, r.role_name 
from userinfo u inner join user_roles ur on u.id = ur.user_id 
inner join roles r on r.id = ur.role_id 
where u.email_address = ?

? in above queries is place holder for user’s login name – which is email_address in current scenario.

Now that we have our queries ready, add Queries to the SecurityConfig class

private final String USER_QUERY =
    "select email_address, password, true as enabled " +
    "from user_info where email_address = ?";


private String USER_ROLE_QUERY =
    "select u.email_address, r.role_name " +
    "from user_info u inner join user_roles ur on u.id = ur.user_id " +
    "inner join roles r on r.id = ur.role_id " +
    "where u.email_address = ?";


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .jdbcAuthentication()
        .dataSource(dataSource)
        .usersByUsernameQuery(USER_QUERY)
        .authoritiesByUsernameQuery(USER_ROLE_QUERY);
}

Now start the application and we are ready to test with new user credential from custom database tables.


curl -i -u user01@tw.com:welcome@123# 'localhost:8080/customer/?pageNum=0&pageSize=2'

curl -i -u admin01@tw.com:admin01@123# 'localhost:8080/customer/?pageNum=0&pageSize=2'

Top ∆

4.3 Internals of Spring Authentication

Now that you have seen how authentication works, you might be wondering how does everything works so easily.

We only specified the query for fetching users and authorities, but how did it work automatically? What is the magic behind it?

The logic behind that resides in the javax.servlet.Filter and org.springframework.security.authentication.AuthenticationProvider classes already present in Spring Boot code.

The most important classes are

  • org.springframework.security.web.authentication.www.BasicAuthenticationFilter
  • org.springframework.security.authentication.dao.DaoAuthenticationProvider

See the sequence diagram below to look at the code flow within Spring Boot’s code that does username/password authentication.

Top ∆

5.0 Autowiring an exisiting User Service for authentication

Till now we have seen multiple ways to authenticate in our application

  • In memory user authentication
  • Authentication with users/role stored in database tables (with standard table names and structure)
  • Authentication with custom user/role tables (by configuring the sql queries to load user and roles)

Although the last one above solved our requirement, but this did not seem a very clean solution. Primary reason being that we had to write explicit SQL queries on how to fetch users and roles, and also autowire a datasource dependency within the SecurityConfig.

Ideally when you have custom classes for users and roles you also might have following already implemented

  • User and Role Entity objects
  • UserRepository
  • UserService that can be used to fetch users and roles.

How do we use this existing UserService for authentication?

  • Role class must implement org.springframework.security.core.GrantedAuthority, and implement
    • String getAuthority() method to return the role name.
  • User class must implement org.springframework.security.core.userdetails.UserDetails, and implement method
    • Collection<? extends GrantedAuthority> getAuthorities() – return all the roles for the user.
    • String getUsername() – return the user’s email address.
    • boolean isAccountNonExpired() – return true.
    • boolean isAccountNonLocked() – return true.
    • boolean isCredentialsNonExpired() – return true.
    • boolean isEnabled() – return true.
  • UserService must implement org.springframework.security.core.userdetails.UserDetailsService, and implement the method
    • UserDetails loadUserByUsername(String username) – fetch user by email address and throw UserNotFoundException if user with the email does not exists.

If you do the above then your UserService is ready to be integrated to the authentication flow.

Go to the class – com.dev.springdemo.config.SecurityConfig, that we had created and remove all the SQL queries and modify the method configure(AuthenticationManagerBuilder auth) as shown below

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{

    @Autowired
    private UserService userService;

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

    // remaining code hidden


}

That is all we need to do to integrate our UserService to authentication flow. You can test the application now by trying to hit any application URL (with username/password).

If you debug the authentication workflow you will notice one change now from aboveJdbcDAOImpl is now replaced by our UserService, and rest of authentication flow is exactly same as before.

Top ∆

This will bring us to the end of this blog. In future blog we will look at how we can skip passing username/password with each url, and use a JWT for authentication.

The entire code of for this project can be found at https://github.com/chatterjeesunit/spring-boot-app/tree/v3.0

To checkout this Release tag, run following command

git clone https://github.com/chatterjeesunit/spring-boot-app.git
cd spring-boot-app
git checkout tags/v3.0 -b v3.0-with-auth


In future articles we will look into how to add auditing, caching, etc to this Spring boot application.