Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Spring Security and Multi-Tenancy

Multi-tenancy in Spring Security allows you to handle multiple tenants (clients) within a single application. This guide covers key concepts and steps for implementing multi-tenancy with Spring Security, including adding dependencies, configuring multi-tenancy, and securing tenant-specific resources.

Key Concepts of Multi-Tenancy

  • Multi-Tenancy: The ability to serve multiple tenants (clients) from a single application instance.
  • Tenant Context: A context that holds information about the current tenant.
  • Security Configuration: Configuring Spring Security to handle tenant-specific authentication and authorization.

Adding Dependencies

Include the Spring Security dependency in your pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Implementing Tenant Context

Create a class to hold tenant information:

Example: TenantContext.java

// TenantContext.java
package com.example.myapp.tenant;

public class TenantContext {

    private static final ThreadLocal currentTenant = new ThreadLocal<>();

    public static void setCurrentTenant(String tenant) {
        currentTenant.set(tenant);
    }

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

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

Creating a Filter to Set Tenant Context

Create a filter to set the tenant context based on the request:

Example: TenantFilter.java

// TenantFilter.java
package com.example.myapp.tenant;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.web.filter.OncePerRequestFilter;

public class TenantFilter 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) {
            TenantContext.setCurrentTenant(tenantId);
        }
        try {
            filterChain.doFilter(request, response);
        } finally {
            TenantContext.clear();
        }
    }
}

Configuring Security for Multi-Tenancy

Ensure Spring Security is configured to handle tenant-specific authentication and authorization:

Example: SecurityConfiguration.java

// SecurityConfiguration.java
package com.example.myapp.config;

import com.example.myapp.tenant.TenantFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new TenantFilter(), UsernamePasswordAuthenticationFilter.class)
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/public/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/home", true)
            .failureUrl("/login?error=true")
            .and()
            .logout()
            .logoutSuccessUrl("/login?logout=true")
            .permitAll();
    }
}

Accessing Tenant Information

Access tenant information in your services or controllers:

Example: MyService.java

// MyService.java
package com.example.myapp.service;

import com.example.myapp.tenant.TenantContext;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    public String getTenantSpecificData() {
        String tenantId = TenantContext.getCurrentTenant();
        // Fetch and return data specific to the tenant
        return "Data for tenant: " + tenantId;
    }
}

Testing Multi-Tenancy

Test your multi-tenancy setup to ensure it works as expected:

Example: MultiTenancyTests.java

// MultiTenancyTests.java
package com.example.myapp;

import com.example.myapp.config.TestSecurityConfig;
import com.example.myapp.tenant.TenantContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@ContextConfiguration(classes = TestSecurityConfig.class)
public class MultiTenancyTests {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Test
    @WithMockUser(username = "user", roles = {"USER"})
    public void testTenantSpecificData() throws Exception {
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
        mockMvc.perform(get("/tenant-data").header("X-Tenant-ID", "tenant1"))
                .andExpect(status().isOk())
                .andExpect(content().string("Data for tenant: tenant1"));
    }
}

Key Points

  • Multi-Tenancy: The ability to serve multiple tenants (clients) from a single application instance.
  • Tenant Context: A context that holds information about the current tenant.
  • Security Configuration: Configuring Spring Security to handle tenant-specific authentication and authorization.
  • Include the Spring Security dependency in your pom.xml file.
  • Create a class to hold tenant information.
  • Create a filter to set the tenant context based on the request.
  • Ensure Spring Security is configured to handle tenant-specific authentication and authorization.
  • Access tenant information in your services or controllers.
  • Test your multi-tenancy setup to ensure it works as expected.

Conclusion

Implementing multi-tenancy in Spring Security allows you to handle multiple tenants within a single application. By understanding and implementing tenant context, configuring Spring Security, and testing your multi-tenancy setup, you can ensure that your Spring Boot application effectively serves multiple tenants. Happy coding!