VOOZH about

URL: https://www.javacodegeeks.com/2025/05/implementing-multi-tenant-security-with-spring-boot-and-spring-security.html

โ‡ฑ Implementing Multi-Tenant Security with Spring Boot and Spring Security - Java Code Geeks


Multi-tenancy is a software architecture where a single application instance serves multiple tenants (clients), each with its own data and configurations. In a multi-tenant system, security is critical โ€” we must ensure that one tenant cannot access the data or resources of another.

This guide walks you through implementing tenant-aware authentication and authorization using Spring Boot and Spring Security, leveraging filter chains to isolate and enforce tenant boundaries.

1. Key Concepts

Before diving into the implementation, letโ€™s clarify the core concepts:

  • Tenant: A distinct client or customer, typically identified by a domain, subdomain, or request header.
  • Authentication: Verifying who the user is.
  • Authorization: Determining what the authenticated user can access.
  • Filter Chain: A sequence of filters that process the request before it reaches the controller.

2. Architecture Overview

Weโ€™ll build the following components:

  1. Tenant Resolver: Resolves tenant information from the incoming request.
  2. Custom Authentication Filter: Validates user credentials in a tenant-aware manner.
  3. Custom Security Filter Chain: Applies tenant-specific rules and separates concerns.
  4. Tenant Context Holder: Stores tenant info per request.
  5. Data Isolation (optional): Ensures tenant-specific data access.

3. Step-by-Step Implementation

1. Project Setup

Add the following dependencies in build.gradle or pom.xml:

dependencies {
 implementation 'org.springframework.boot:spring-boot-starter-security'
 implementation 'org.springframework.boot:spring-boot-starter-web'
}

2. Tenant Context Holder

We use a ThreadLocal to store tenant info per request lifecycle:

public class TenantContext {
 private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();

 public static void setTenantId(String tenantId) {
 currentTenant.set(tenantId);
 }

 public static String getTenantId() {
 return currentTenant.get();
 }

 public static void clear() {
 currentTenant.remove();
 }
}

3. Tenant Resolver Filter

Extract the tenant ID from headers or subdomains.

@Component
public class TenantResolverFilter extends OncePerRequestFilter {

 @Override
 protected void doFilterInternal(HttpServletRequest request,
 HttpServletResponse response,
 FilterChain filterChain) throws ServletException, IOException {
 String tenantId = request.getHeader("X-Tenant-ID");

 if (tenantId != null && !tenantId.isEmpty()) {
 TenantContext.setTenantId(tenantId);
 }

 try {
 filterChain.doFilter(request, response);
 } finally {
 TenantContext.clear(); // prevent memory leaks
 }
 }
}

4. Custom Authentication Filter

A simplified filter that handles tenant-specific authentication logic:

public class TenantAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

 private final AuthenticationManager authenticationManager;

 public TenantAuthenticationFilter(AuthenticationManager authenticationManager) {
 this.authenticationManager = authenticationManager;
 setFilterProcessesUrl("/login");
 }

 @Override
 public Authentication attemptAuthentication(HttpServletRequest request,
 HttpServletResponse response)
 throws AuthenticationException {

 String username = request.getParameter("username");
 String password = request.getParameter("password");
 String tenantId = TenantContext.getTenantId();

 UsernamePasswordAuthenticationToken authToken =
 new UsernamePasswordAuthenticationToken(username + "|" + tenantId, password);

 return authenticationManager.authenticate(authToken);
 }
}

5. Custom Authentication Provider

Handle tenant-aware logic in the provider:

@Component
public class TenantAuthenticationProvider implements AuthenticationProvider {

 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 String[] parts = ((String) authentication.getPrincipal()).split("\\|");
 String username = parts[0];
 String tenantId = parts[1];
 String password = (String) authentication.getCredentials();

 // Fetch user based on username and tenantId from DB
 if ("user".equals(username) && "tenant1".equals(tenantId) && "pass".equals(password)) {
 return new UsernamePasswordAuthenticationToken(username, password,
 Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
 }

 throw new BadCredentialsException("Invalid credentials for tenant: " + tenantId);
 }

 @Override
 public boolean supports(Class<?> authentication) {
 return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
 }
}

6. Configure Spring Security

Register filters and authentication provider:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

 @Autowired
 private TenantAuthenticationProvider tenantAuthProvider;

 @Autowired
 private TenantResolverFilter tenantResolverFilter;

 @Bean
 public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
 AuthenticationManager authManager = authenticationManager(http);

 http
 .csrf().disable()
 .authorizeHttpRequests(authz -> authz
 .requestMatchers("/public/**").permitAll()
 .anyRequest().authenticated()
 )
 .addFilterBefore(tenantResolverFilter, UsernamePasswordAuthenticationFilter.class)
 .addFilterBefore(new TenantAuthenticationFilter(authManager), UsernamePasswordAuthenticationFilter.class)
 .authenticationProvider(tenantAuthProvider);

 return http.build();
 }

 @Bean
 public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
 return http.getSharedObject(AuthenticationManagerBuilder.class)
 .authenticationProvider(tenantAuthProvider)
 .build();
 }
}

๐Ÿงช Sample Request

curl -X POST http://localhost:8080/login \
 -H "X-Tenant-ID: tenant1" \
 -d "username=user&password=pass"

4. Data Isolation Strategy (Optional but Recommended)

If youโ€™re using a database per tenant or shared DB with tenant IDs:

  • Use Hibernateโ€™s @FilterDef or Spring Data interceptors.
  • Use multi-tenant JPA configuration using AbstractRoutingDataSource.

5. Best Practices

  • Validate tenant existence early in the filter.
  • Avoid global static context outside of request scope.
  • Log tenant ID in request/response logs.
  • Secure default tenant fallback behavior (e.g., reject if tenant ID is missing).

6. Extensions

  • Integrate with OAuth2 or JWT for stateless multi-tenant security.
  • Use Spring Cloud Gateway for subdomain-based tenant routing.
  • Add a TenantDetailsService for loading user data per tenant.

7. Conclusion

Multi-tenant security requires isolating not only data but also authentication and authorization logic per tenant. By leveraging custom filters, Spring Security, and a clean tenant context design, you can build secure, scalable applications that serve multiple clients reliably.

Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Thank you!

We will contact you soon.

๐Ÿ‘ Photo of Eleftheria Drosopoulou
Eleftheria Drosopoulou
May 21st, 2025Last Updated: May 18th, 2025
0 1,301 3 minutes read

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button
Close
wpDiscuz