Dynamic Multi-Tenancy Using Spring Security and JWTs
Dynamic Multi-Tenancy Using Spring Security and JWTs
In this article, we discuss how to enable multi-tenancy in a web application with Spring Security and JWTs.
Join the DZone community and get the full member experience.Join For Free
I wanted a solution where multi-tenancy is achieved by having a database per-tenant and all user information (username, password, client Id, etc.) for authentication and authorization stored in a user table in the respective tenant databases. This means that not only did I need a multi-tenant application, but also a secure application like any other web application secured by Spring Security.
I know how to use Spring Security to secure a web application and how to use Hibernate to connect to a database. The requirement further dictates that all users belonging to a tenant need to be stored in the tenant database and not a separate or central database. This would allow for complete data isolation for each tenant.
- Archive Application SaaS Model client wise different database.
- Focus Spring Security and JWT
- You can connect multiple schemas with a single database, like MySQL — testdb, testdb2.
- You can connect multiple databases, like MySQL, PostgreSQL, or Oracle.
What Is Multi-Tenancy?
Multi-tenancy is an architecture in which a single instance of a software application serves multiple customers. Each client is called a tenant. Tenants may be given the ability to customize some parts of the application.
A multi-tenant application is where a tenant (i.e. users in a company) feels that the application has been created and deployed for them. In reality, there are many such tenants, and they too are using the same application but get a feeling that it's built just for them.
Dynamic Multi-Tenant High-Level Diagram: Here,
- Client requests to login to the system.
- The system checks with the master database using client Id.
- If it's successful, set the current database to context based on the driver class name.
- If this fails, the user gets the message, "unauthorized".
- After successful authentication, the user gets a JWT for the next execution.
The whole process executes in the following workflow:
Now, Let's start developing a multi-tenancy application step-by-step with Spring Security and JWT.
1. Technology and Project Structure:
- Java 11.
- Spring Boot.
- Spring Security.
- Spring AOP.
- Spring Data JPA.
- MySQL, PostgreSQL.
You can get started quickly by using https://start.spring.io/.
2. Now, Create a Master Database and a tenant database.
In the master database, we only have one table (
tbl_tenant_master), where all tenant information is storeed in the table.
Tenant Database (1) in MySQL:
Create a table for client login authentication(
Create another table (
tbl_product) to retrieve data using a JWT (for Authorization checks).
Tenant Database (2) in PostgreSQL:
Create a table for client login authentication (
Create another table (
tbl_product) to retrieve data using a JWT (for authorization checks).
Database creation and table creation are done!
3. Check the
4. Configure the master database or common database into our Spring Boot application (
5. Spring Security and Enabling JWT:
WebSecurityConfigurerAdapter allows users to configure web-based security for a certain selection (in this case all) requests. It allows configuring things that impact our application's security.
WebSecurityConfigurerAdapter is a convenience class that allows customization to both
OncePerRequestFilter, is a filter base class that aims to guarantee a single execution per request dispatch on any servlet container. As of Servlet 3.0, a filter may be invoked as part of a REQUEST or ASYNC dispatch that occurs in separate threads.
ExceptionTranslationFilter is used to catch any Spring Security exceptions so that either an HTTP error response can be returned, or an appropriate
AuthenticationEntryPoint can be launched. The
AuthenticationEntryPoint will be called if the user requests a secure HTTP resource, but they are not authenticated.
6. Configure Master Database :
Master Data Source Configuration:
ThreadLocals is used to maintain some context related to the current thread. For example, when the current transaction is stored in a
ThreadLocal, you don't need to pass it as a parameter through every method call in case someone down the stack needs access to it.
Web applications might store information about the current request and session in a
ThreadLocal, so that the application has easy access to them.
ThreadLocals can be used when implementing custom scopes for injected objects.
ThreadLocals are one sort of global variables (although slightly less evil because they are restricted to one thread), so you should be careful when using them to avoid unwanted side-effects and memory leaks.
Create another class,
MasterDatabaseConfigProperties.java. It holds a connection-related parameter, as defined in the
<tx:annotation-driven/> are responsible for registering the necessary Spring components that power annotation-driven transaction management, such as the
TransactionInterceptor and the proxy, or an AspectJ-based advice that weaves the interceptor into the call stack when JdbcFooRepository's
@Transactional methods are invoked.
7. Configure Tenant Database.
In this section, we'll work to understand multitenancy in Hibernate. There are three approaches to multitenancy in Hibernate:
- Separate Schema — one schema per tenant in the same physical database instance.
- Separate Database — one separate physical database instance per tenant.
- Partitioned (Discriminator) Data — the data for each tenant is partitioned by a discriminator value.
As usual, Hibernate abstracts the complexity around the implementation of each approach.
All we need is to provide an implementation of these two interfaces:
MultiTenantConnectionProvider– provides connections per tenant.
CurrentTenantIdentifierResolver– resolves the tenant identifier to use.
If Hibernate cannot resolve the tenant identifier to use, it will use the method,
getAnyConnection, to get a connection. Otherwise, it will use the method,
Hibernate provides two implementations of this interface depending on how we define the database connections:
- Using Datasource interface from Java – we would use the DataSourceBasedMultiTenantConnectionProviderImpl implementation
- Using the ConnectionProvider interface from Hibernate – we would use the AbstractMultiTenantConnectionProvider implementation
Hibernate calls the method,
resolveCurrentTenantIdentifier, to get the tenant identifier. If we want Hibernate to validate that all the existing sessions belong to the same tenant identifier, the method
validateExistingCurrentSessions should return true.
In this strategy, we'll use different schemas or users in the same physical database instance. This approach should be used when we need the best performance for our application and can sacrifice special database features such as backup per tenant.
The Database multi-tenancy approach uses different physical database instances per tenant. Since each tenant is fully isolated, we should choose this strategy when we need special database features, like backup per tenant more than we need the best performance.
It seems like we're almost done. So we should move to the next step in the process.
8. Database Data checks:Master Database data:
Tenant Database (MySQL) Table Data:
Tenant Database (PostgreSQL) Tables Data:
9. Now, test that everything works as we expect using Postman:
I hope, this tutorial will be helpful for any person or organization. I tried to show how to enable multi-tenancy in your Spring Boot application using Spring Security and JWT.
You can find source code in link: https://github.com/amran-bd/Dynamic-Multi-Tenancy-Using-Java-Spring-Boot-Security-JWT-Rest-API-MySQL-Postgresql-full-example.
Opinions expressed by DZone contributors are their own.