Spring Boot Security Configuration, practically explained – Part2: JDBC Authentication
220426 – 220510
Intro
This is the 2nd in a series of posts about the Spring Security customization based on the implementation of a custom class extending the WebSecurityConfigurerAdapter abstract class. You can access the 1st post below:
Spring Boot Security Configuration, practically explained – Part1: Starting with Spring Boot Security and Basic HTTP Authentication
Notes:
The legacy Spring Boot Security Configuration (extending the WebSecurityConfigurerAdapter abstract class), has been deprecated and is being replaced by a component-based security configuration. However, since the existing coding base is huge, here, we are still using the WebSecurityConfigurerAdapter, and we will try to provide a better understanding, using practical examples.
If you don’t wish to continue seeing the warning curly yellow lines in your IDE when you use the latest versions of Spring Boot, you should use (switch to) an earlier version of Spring Security (spring.security dependency. Here is how you can do that.
JDBC Authentication
Prerequisites
As you should be already aware, JDBC stands for Java Database Connectivity, so, here, JDBC Authentication presupposes a functioning connection with a Database layer, as well as an appropriate working database schema. For this post, we will use MariaDB a binary replacement of MySQL Database. So, if you haven’t access to such a Database, here are 2 posts of mine that can help you with that:
– Installing and Using MariaDB via Docker
– Install MariaDB (10.5.8.-GA) via Homebrew on macOS
Furthermore, for the purposes of our work here, you can also access this GitHub repository. The code provided is ready for a MariaDB connection (as it is described in this post, here). Also, it has been configured for simple in-memory authentication (as in Part 1 of this series, here). Use the code in the repository as the basis for this post.
Intro to JDBC Authentication
JDBC Authentication implementation can be achieved in various ways offered by the Spring Security framework. Here we are going to go through 3 different approaches:
- Using the default JDBC Authentication and a pure UserDetailsService Bean
- Using the jdbcAuthentication() and the JdbcUserDetailsManagerConfigurer
- Using the DaoAuthenticationProvider with custom UserDetailsService
1. Using the default JDBC Authentication with UserDetailsService
As we have said, Spring Security offers an interface named UsersDetailsService. This is the core interface to load user-specific data (user details). The implementation of the UsersDetailsService requires the implementation of only one method, the loadUserByUsername method. The UsersDetailsService implementation can be used to find a user based on the username provided. As we have previously presented, the UsersDetailsService can be used by Spring security via the DaoAuthenticationProvider for retrieving a user object/entity on the basis of the user’s credentials, username, and password in Basic Authentication.
Intro to default JDBC Authentication
In case we use a JDBC datasource, we can use the default JDBC Authentication mechanism. This includes the JdbcDaoImpl class which is an implementation class of UserDetailsService interface.
By default, for user authentication, JdbcDaoImpl requires a specific database schema with 2 tables (and their relations). These are the tables: users and authorities.
The Users table Columns
- username
- password
- enabled
The Authorities Table Columns
- username
- authority
Create database tables and insert some data
For instance, the following sql statements defines such a simple schema with the above 2 tables:
Of course, we can always use more columns for each one of the tables and more relations between them. For, instance, in the above example, we use 2 more fields/columns: id and email. This is OK, since the mandatory fields, for the default JSON Authentication mechanism, (username, password, and enabled) are included in the table.
Then , we have to add some data in the tables. For example, use the following SQL statements:
Notes:
No values for id column in the users table
We do not insert values for the id field in table users, because we have created it as ‘AUTO_INCREMENT‘
Password Encryption
As you probably have noticed, we use hashed passwords, encoded with BCrypt encoding. This is necessary because we can not use plain passwords anymore (as we have said in the previous post here). To encode any password using the BCrypt algorithm, you can use either the encodepassword command with Spring Boot CLI or one of the available free online services, e.g. here. Later on, we will also use a BCryptPasswordEncoder password encoder, defined as a @Bean.
Moreover, it’s worth underlying here that using the backend layer -as a Spring Boot application, like here we do- probably is not the best practice. Another option you have is to do this inside the database layer, using some other database objects, such as triggers and/or stored procedures/functions. This is considered more secure, since neither the password nor the password hash algorithm being used, are exposed outside the database (when both, the backend server and the database server are located in the same internal network and/or behind firewalls).
Values in authority column in the authorities table
There is also a default naming requirement for authority column values. They should have preceded by the prefix ‘ROLE_’. As we will see later on, this is necessary when we use the .antMatchers with .hasRole, overriding the HttpSecurity configure method in our WebSecurityConfig class (which of course extends WebSecurityConfigurerAdapter).
Application settings
Ensure that the necessary dependencies are included in the pom.xml file:
Similarly, these are the connectivity settings for the MariaDB database in the app’s application.properties file:
Defining a Bean of the UsersDetailsService in Spring Security Configuration
So, we are ready to proceed further in customizations via the HTTPSecurity configurer. We can do that by authorizing incoming requests (http.authorizeRequests()), prohibiting or allowing access for more endpoints/paths and/or roles. It’s up to you and your application needs.
But now, we will proceed to configure Authentication using a more sensible approach than the unaccepted IinMemoryAuthentication(). Actually, we are going to use the JDBC-based authentication implementation. We will do it by defining a Spring annotation-based @Bean.
Implementing a UserDetailsService Bean via JdbcDaoImpl class
What we will do, is to use the default Bean of the UserDetailsService, instantiating it passing into it the datasource object.
The datasource object
Note that the datasource object comes out automatically, by Spring, from the application context. This means that you don’t have to write some code for it. Below you can take a look at the documentation about it:
javax.sql.DataSource
A factory for connections to the physical data source that this DataSource object represents. An alternative to the DriverManager facility, a DataSource object is the preferred means of getting a connection. An object that implements the DataSource interface will typically be registered with a naming service based on the Java Naming and Directory (JNDI) API.
The DataSource interface is implemented by a driver vendor. There are three types of implementations:
1. Basic implementation — produces a standard Connection object
2. Connection pooling implementation — produces a Connection object that will automatically participate in connection pooling. This implementation works with a middle-tier connection pooling manager.
3. Distributed transaction implementation — produces a Connection object that may be used for distributed transactions and almost always participates in connection pooling. This implementation works with a middle-tier transaction manager and almost always with a connection pooling manager.
A DataSource object has properties that can be modified when necessary. For example, if the data source is moved to a different server, the property for the server can be changed. The benefit is that because the data source’s properties can be changed, any code accessing that data source does not need to be changed.
A driver that is accessed via a DataSource object does not register itself with the DriverManager. Rather, a DataSource object is retrieved through a lookup operation and then used to create a Connection object. With a basic implementation, the connection obtained through a DataSource object is identical to a connection obtained through the DriverManager facility.
An implementation of DataSource must include a public no-arg constructor.
Since: 1.4
Actually, the datasource object is based on what we have defined as a database in our pom.xml file, and its connection parameters -as we’ve seen before in our application.properties file.
After the short reference to datatsource object, below is an example of a UserDetailsService Bean using the instantiation of the default JdbcDaoImpl class:
Or, since the JdbcDaoImpl class implements the UserDetailsService interface we can use this interface, in the Bean’s “signature”, instead:
This is an example of how we can benefit from the “ready-made” implementations, the Spring framework offers us, which ‘heavily’ follows Java OOP. Above, in the 1st snippet, we can use just the offered JdbcDaoImpl class, without going deeper.
If you have accessed the official documentation for UserDeatilsService, you should have probably noticed that Spring offers us a number of other implementation classes, (one of which is the JdbcDaoImpl class):
So, in the 2nd snippet, we used the UserDetailsService interface instead of the JdbcDaoImpl class. This is because our aim is to show -in a step-by-step manner- how we can proceed further in customizations and finally -instead of using a “ready-made” class, implement our own UserDetailsService.
Implementing a UserDetailsService Bean via JdbcUserDetailManager
As you can also see above there is another interesting “ready-made” class, -that also implements the UserDetailsService- the JdbcUserDetailsManager. So, as a next step, we can use this class, which extends the JdbcDaoImpl and implements directly an instance of the default UserDetailsService interface (through the UserDetailsManager interface). In order to instantiate it, we just have to pass it, the datasource object:
or even better – less code:
Well done! Now we can use just the above @Bean, leaving the Spring doing the rest for us. The Bean returns just a UserDetailsService object (it is actually a JdbcDaoImpl object) by creating it through the JdbcUserDetailsManager (which accepts our datasource as a parameter). No further configuration (via the AuthenticationManagerBuilder) is needed for now. However, we can configure a bit further the http.authorizeRequests().
The Custom Spring Security Configuration class
Gathering all together, below is a working custom implementation of the Spring Security configuration based on WebSecurityConfigurerAdapter interface, and using only the defaults of JDBC authentication provided by the Spring framework:
What it actually does, is to protect the /api/items endpoint, allowing only access to authenticated users having a role, the ADMIN role (‘ROLE_ADMIN’).
Notes:
Password Encoder Bean
You should have noticed, that we have also added a Password Encoder Bean that returns a BCryptPasswordEncoder, object. As we have already mentioned, this is necessary for encoding / decoding the passwords using the BCrypt algorithm.
Session: STATELESS
Also, note that we have made our Session STATELESS:
. . .
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
. . .
If we haven’t done it, we would have left the session to be STATEFULL (the default). So, when a user has been authenticated just once, then he could continue accessing the endpoint after that, even without providing his credentials (username/password) again or even providing the wrong credentials. This might be OK for WEB applications (allowing the user to use various pages, without providing his credentials again and again. But making the session STATELESS is the most appropriate approach in REST API service.
Find the final code in the repo here.
We will stay a bit more here, to get a glance at an interesting option for making some more customizations.
Customization of queries retrieving user details
So far, we did nothing more than just use the JDBC authentication defaults. However, we can take one more step, and apply some kind of customizations. This is doable even when using the JDBC authentication defaults. Actually, this is due to the JdbcDaoImpl class (which of course implements the UserDetailsService interface). The JdbcDaoImpl implements/uses the interface method loadUserByUsername to locate the user in the default schema (remember: users and authorities tables). This means that we can override this method, and thus, for instance, we can retrieve a user object with a username different than the one provided, eg. case sensitivity, etc. Moreover, the JdbcDaoImpl class offers a number of other methods that can be used, as well as, users Group support. This is the list of the properties/methods offered, as well as of the query string constants being used:
Here, the important thing, as you can see above, is the fact that we can make some customizations, in defining and setting our own queries instead of using the default ones. For instance, we can use the setUsersByUsernameQuery property passing our own query string. This is useful when for example, you use different names for your database objects (table names, column names, etc.):
This is not that big deal, though, it gives us some more flexibility to work with our custom schema, dealing with tables and columns with different/custom names, as well as to add some more requirements for user retrieving (e.g. enabled = true). So, you can use it according to your needs, e.g. for some simpler implementations.
2. Using the jdbcAuthentication() and JdbcUserDetailsManagerConfigurer
So far we have provided a UserDetailsService @Bean into the Custom Security Configuration without using the .configure() method for direct customizations.
The next step is to implement a security configuration using the jdbcAuthentication() and the JdbcUserDetailsManagerConfigurer. However, before proceeding, it’s worth mentioning again the importance of UserDetails.
UserDetails
What actually is quite important to understand, is that any implementation of the UserDetailsService interface (such as the JdbcDaoImpl implementation class, which uses the loadUserByUsername method, we’ve seen earlier), returns an instance of an authenticated user object of the type of UserDetails interface, providing the user core information. The UserDetails interface defines the following methods/properties:
As it is stated in the official documentation above, implementations of that interface are not used directly by Spring Security for security purposes. They simply store user information which is later encapsulated into Authentication objects. This also allows non-security-related user information (such as email addresses, telephone numbers, etc,) to be stored in a convenient location.
Of particular interest is the getAuthorities() method that returns the authorities that are granted (or should be granted) to the authenticated user. As you probably are aware, authentication is one thing and authorization is another. So, apart from the existence/validation/authentication of a user, security concepts include also the authorization rules on the basis of which, an authenticated user can have/grant access to application resources (endpoints, pages, etc.). A collection of authorities that are granted to a user plays exactly this role. The authorities (and/or roles) are being used in spring HTTP security (via antMatchers and .hasRole or .hasAuthority) to allow or restrict an authenticated user to access the application resources. We will see how to implement this, later on.
So, again, a UserDetailsService implementation that creates a User object with his/her authorities is what should be available within a Custom Spring Security Configuration class.
When we will work with DaoAuthenticationProvider, we will also create and use a custom UserDetailsService class. This will be a later step. Now we are going to use the jdbcAuthentication() and JdbcUserDetailsManagerConfigurer.
jdbcAuthentication() and JdbcUserDetailsManagerConfigurer
In the very beginning (in this post here), we used the inMemoryAuthentication() configurer with the AuthenticationManagerBuilder. This was a very basic example. But the important was that we used the AuthenticationManagerBuilder configurer. Using a configurer, apart from it is suitable for relatively simpler security configurations, it also allows us to build/configure directly an authentication schema within our custom Security Configuration class.
Here we are going to use the jdbcAuthentication() configured. Below you can take a look at the documentation about it:
jdbcAuthentication
public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
throws Exception
Add JDBC authentication to the AuthenticationManagerBuilder and return a JdbcUserDetailsManagerConfigurer to allow customization of the JDBC authentication.
When using with a persistent data store, it is best to add users external of configuration using something like Flyway or Liquibase to create the schema and adding users to ensure these steps are only done once and that the optimal SQL is used.
This method also ensure that a UserDetailsService is available for the getDefaultUserDetailsService() method. Note that additional UserDetailsService‘s may override this UserDetailsService as the default. See the User Schema section of the reference for the default schema.
Returns:
a JdbcUserDetailsManagerConfigurer to allow customization of the JDBC authentication
Throws:
Exception – if an error occurs when adding the JDBC authentication
Before proceeding, it is necessary to recall, that this can be done by using the jdbcAuthentication() method provided by the Spring Security class AuthenticationManagerBuilder. The jdbcAuthentication() method, adds JDBC authentication to the AuthenticationManagerBuilder and returns a JdbcUserDetailsManagerConfigurer to allow direct customization of the JDBC authentication.
Below, is a simple example of how we can quickly implement the built-in ‘jdbcAuthentication’ provided by the Spring Boot framework:
Notes:
Note, that, in the above example, we also define our own custom queries, and then we pass them into the configurer. The question marks “?” in the queries are being used as placeholders, for the credentials provided in the Basic Authorization header of the HTTP request.
JdbcTemplate
Interesting also is to mention that we use the JdbcTemplate which is a central class in the JDBC core package. Using just it, we can define and execute our own raw SQL queries, without any additional ORM, e.g. JPA/Hibernate).
The jdbcAuthentication requires an instance of a DataSource object, and we can get it via the jdbcTemplate.getDataSource() method. As it has been already mentioned before, the Spring mechanism automatically creates (and autowires) a datasource bean from the database physical connection properties provided in the application.properties file. (Actually, Spring can detect and use the database driver, based on what is available on the classpath). And the same is true for creating and autowiring a jdbcTemplate object (bean). We only have to provide (inject) it in our custom Security Configuration class, using the @Autowire annotation:
Coming back to our implementation, using jdbcAuthentication() and JdbcUserDetailsManagerConfigurer, we can say that it gives us more flexibility, but it is not that far away from the default JDBC Authentication (with a simple UserDetailsService Bean), which was discussed before.
The most important thing to keep in your mind from this step is that we can always use an appropriate configurer to directly configure our authentication manager.
Find the final code in the repo here.
3. Using the DaoAuthenticationProvider with custom UserDetailsService
So far, we have seen both approaches to configure a UserDetailsService, using the .configure() method -the configurer- and using a @Bean, thinking that the last one counts as a better approach for relative bigger projects. So, in the next step here, we are going to implement a DaoAuthenticationProvider @bean, however, this time we will make our own custom class of the UsersDetailsService implementing the UserDetailsInterface.
A short Intro to DaoAuthenticationProvider mechanism
As we have mentioned in the previous post here, the DaoAuthenticationProvider is one of the classes that implement an AuthenticationProvider interface. The DaoAuthenticationProvider can be used to leverage a UserDetailsService and/or PasswordEncoder to authenticate a username and password.
- The authentication
Filter
from Reading the Username & Password passes aUsernamePasswordAuthenticationToken
to theAuthenticationManager
which is implemented byProviderManager
. - The
ProviderManager
is configured to use an AuthenticationProvider. In our case this could be of typeDaoAuthenticationProvider
. DaoAuthenticationProvider
looks up theUserDetails
from theUserDetailsService
.DaoAuthenticationProvider
then can use thePasswordEncoder
to validate the password on theUserDetails
returned in the previous step.- When authentication is successful, the
Authentication
that is returned is of typeUsernamePasswordAuthenticationToken
and has a principal that is theUserDetails
returned by the configuredUserDetailsService
. Ultimately, the returnedUsernamePasswordAuthenticationToken
will be set on theSecurityContextHolder
by the authenticationFilter
.
Custom implementation class of the UserDetailsService interface
We have already seen the mechanism of the default JdbcSaoImpl implementation of the UserDetailsService interface. However, we can always go further and implement our very own UserDetailsService leaving aside the Spring provided JdbcDaoImpl class. Thus, we are now ready to implement our custom UserDetailsService class.
However, the UserDetailsService implementation requires fetching some data from our database. We can do that from within the implementation of the UserDetailsService class or implement a separate class as a data repository. Such a repo can be useful to deal with our database for other tasks as well, beyond the purposes of fetching authentication/authorization user’s data. So the repo can be also the place for various other queries dealing with the database, serving, for instance, other endpoints for our app.
In the following real example, we use a custom UserDetailsClass (the UserService), which uses a repository -the UsersRepo – to obtain the user details.
What we do, is actually to return a new org.springframework.security.core.userdetails.User object. This User object is instantiated by providing it, the username, password, and authorities (roles) obtained from our own repository.
Below, is also a working example of a repository (the UsersRepo repository)
As you can see, the UsersRepo repository, is based again only in the in JdbcTemplate and uses “pure” SQL queries (as we said, No ORMs, No JPA/Hibernate).
Finally, we are ready to go for our custom Security Configuration class and define a DaoAuthenticationProvider as a bean, (passing it the custom UserDetailsService). Below, is our example of a custom Security Configuration class (the SecurityConfiguration class):
Find the final code in the repo here.
As you can understand the final implementation offers us much more flexibility and a good grade of separation of concerns. Moreover, having our custom UserDetailsService in place we can go further and leverage it with other authentication mechanisms, such as the LDAP Authentication, which we are going to see in the next post in this series.
For now, that’s it!
Enjoy, and thank you for reading!