# Understanding the Multi-Tenant Architecture in KCloud-Platform-IoT

> Explore the multi-tenant architecture in KCloud-Platform-IoT. Learn how it leverages tenant code propagation, OAuth2, and thread-local routing for secure SaaS multi-tenancy and isolated customer data.

- Repository: [laokou/kcloud-platform-iot](https://github.com/koushenhai/kcloud-platform-iot)
- Tags: architecture
- Published: 2026-03-05

---

**KCloud-Platform-IoT implements true SaaS multi-tenancy by combining tenant code propagation, thread-local context routing, and OAuth2 multi-issuer support to isolate customer data while sharing a single runtime instance.**

The `koushenhai/kcloud-platform-iot` repository provides a production-grade IoT platform designed for multi-tenant deployments. Understanding the multi-tenant architecture in KCloud-Platform-IoT reveals how the system achieves complete logical isolation between tenants—represented as distinct customers or business units—while maintaining operational efficiency through shared infrastructure.

## Core Mechanisms of Tenant Isolation

The architecture coordinates four distinct layers to enforce tenant boundaries: identifier propagation, resolution, data routing, and authentication context.

### Tenant Identifier Propagation

Every request originating from the UI carries a human-readable `tenant_code` that identifies the target tenant. This identifier flows through HTTP headers and API payloads across the entire request lifecycle.

In [`ui/src/pages/Login/index.tsx`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/ui/src/pages/Login/index.tsx), the login form explicitly includes the tenant context:

```tsx
const handleSubmit = async (values) => {
  await request.post('/v1/oauth2/token', {
    username: values.username,
    password: values.password,
    tenant_code: values.tenant_code,   // Human-readable tenant identifier
    grant_type: 'password',
    scope: 'all',
  });
};

```

The TypeScript definitions in [`ui/src/services/auth/typings.d.ts`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/ui/src/services/auth/typings.d.ts) formalize this contract, while [`ui/src/services/admin/tenant.ts`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/ui/src/services/admin/tenant.ts) provides the administrative CRUD APIs for managing tenant records.

### Tenant Resolution and Thread-Local Context

Upon receiving a request, the backend translates the `tenant_code` into a numeric `tenantId` using `TenantGatewayImpl`. This gateway queries the `sys_tenant` table and stores the resolved ID in a thread-local `DataSourceContext`, ensuring all subsequent operations within the request thread are scoped to the correct tenant.

The resolution logic resides in [`laokou-auth-infrastructure/src/main/java/org/laokou/auth/gatewayimpl/TenantGatewayImpl.java`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/laokou-auth-infrastructure/src/main/java/org/laokou/auth/gatewayimpl/TenantGatewayImpl.java):

```java
@Component
@RequiredArgsConstructor
public class TenantGatewayImpl implements TenantGateway {
    private final TenantMapper tenantMapper;

    @Override
    public Long getTenantId(String tenantCode) {
        return tenantMapper.selectIdByCode(tenantCode);
    }
}

```

This thread-local storage mechanism guarantees that downstream service calls automatically inherit the correct tenant context without manual parameter passing.

### Data-Source Routing and Row-Level Security

All persistent data objects contain a `tenant_id` column (e.g., `sys_user`, `sys_role`, `sys_oss`). The `DSConstants` class centralizes table name constants, while a MyBatis interceptor automatically appends `WHERE tenant_id = :currentTenantId` to every SQL statement targeting tenant-scoped tables.

The constants are defined in [`laokou-common-tenant/src/main/java/org/laokou/common/tenant/constant/DSConstants.java`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/laokou-common-tenant/src/main/java/org/laokou/common/tenant/constant/DSConstants.java):

```java
public final class DSConstants {
    public static final class Master {
        public static final String TENANT_TABLE = "sys_tenant";
        // Additional table constants...
    }
}

```

Data objects like [`UserDO.java`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/UserDO.java), [`RoleDO.java`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/RoleDO.java), and [`OssLogDO.java`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/OssLogDO.java) import these constants and include the `tenantId` field, enabling the interceptor to apply row-level filters transparently.

### OAuth2 Multi-Issuer Configuration

The authorization server supports multiple issuers under the same host through path-based differentiation. Setting `multipleIssuersAllowed = true` in `OAuth2AuthorizationServerProperties` enables this mode, which is essential for multi-tenant deployments where each tenant may require a distinct issuer URL.

Configuration in [`laokou-auth-infrastructure/src/main/java/org/laokou/auth/config/OAuth2AuthorizationServerProperties.java`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/laokou-auth-infrastructure/src/main/java/org/laokou/auth/config/OAuth2AuthorizationServerProperties.java):

```yaml
spring:
  security:
    oauth2:
      authorization-server:
        multiple-issuers-allowed: true   # Enables tenant-specific issuer paths

```

## End-to-End Authentication Flow

The multi-tenant architecture operates through a coordinated sequence across all layers:

1. **Login Initiation**: The UI posts credentials along with `tenant_code` to `/v1/oauth2/token` as shown in [`Login/index.tsx`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/Login/index.tsx).
2. **Issuer Resolution**: The auth server checks `multipleIssuersAllowed` and determines the appropriate issuer path for the tenant.
3. **Tenant Lookup**: `TenantGatewayImpl.getTenantId(tenantCode)` queries `sys_tenant` via `TenantMapper`, and the numeric ID attaches to the thread-local context.
4. **Database Filtering**: MyBatis interceptors inject the `tenant_id` constraint into all queries, ensuring only the current tenant's rows are accessible.
5. **Token Generation**: The resulting JWT includes a `tenantId` claim, allowing downstream services to validate tenant context from the security context.

## Practical Implementation Examples

### Frontend Tenant Submission

When authenticating users, the React frontend must include the tenant code:

```tsx
// ui/src/pages/Login/index.tsx
const handleSubmit = async (values) => {
  await request.post('/v1/oauth2/token', {
    username: values.username,
    password: values.password,
    tenant_code: values.tenant_code,
    grant_type: 'password'
  });
};

```

### Backend Service Integration

Services resolve the tenant ID and rely on automatic SQL filtering:

```java
@Service
@RequiredArgsConstructor
public class UserService {
    private final TenantGateway tenantGateway;
    private final UserMapper userMapper;

    public UserDTO getCurrentUser(String tenantCode, String username) {
        Long tenantId = tenantGateway.getTenantId(tenantCode);
        // TenantInterceptor automatically adds tenant_id filter
        UserDO user = userMapper.selectByUsername(username);
        return UserConvert.INSTANCE.toDto(user);
    }
}

```

### Authorization Server Configuration

Enable multi-tenant hosting in the application configuration:

```yaml

# laokou-auth-start/src/main/resources/application.yml

spring:
  security:
    oauth2:
      authorization-server:
        multiple-issuers-allowed: true
tenant:
  default-code: master   # Fallback for single-tenant mode

```

### Custom MyBatis Queries

For manual SQL, reference the tenant ID placeholder auto-populated by the interceptor:

```xml
<select id="selectByTenant" resultType="UserDO">
  SELECT *
  FROM ${DSConstants.Master.USER_TABLE}
  WHERE username = #{username}
  <if test="_tenantId != null">
    AND tenant_id = #{_tenantId}
  </if>
</select>

```

## Summary

- **Tenant Identification**: KCloud-Platform-IoT uses a dual-identifier system combining human-readable `tenant_code` with numeric `tenantId` for database operations.
- **Automatic Isolation**: The `TenantGatewayImpl` class and thread-local `DataSourceContext` ensure tenant context propagates through the entire call stack without manual intervention.
- **Row-Level Security**: MyBatis interceptors automatically append tenant filters to SQL queries based on `DSConstants` table definitions, preventing cross-tenant data leakage.
- **OAuth2 Scalability**: The `multipleIssuersAllowed` configuration supports distinct issuer URLs per tenant while maintaining a single authorization server instance.

## Frequently Asked Questions

### What is a tenant in KCloud-Platform-IoT?

A tenant represents an isolated customer or business unit operating within the shared SaaS infrastructure. Each tenant maintains independent data, user management, and authentication contexts while running on the same physical application instance. The system uses the `sys_tenant` table (defined in `DSConstants`) as the master registry for all tenant metadata.

### How does tenant isolation work at the database level?

Tenant isolation relies on a **Shared Database, Separate Schemas/Row Filtering** approach. Every table subject to multi-tenancy includes a `tenant_id` column. The common tenant module's MyBatis interceptor automatically injects `WHERE tenant_id = :currentTenantId` into SQL statements, ensuring queries only return rows belonging to the current thread's tenant context. This filtering occurs transparently without requiring developers to manually add constraints to every query.

### Can multiple tenants share the same OAuth2 authorization server?

Yes. By setting `multipleIssuersAllowed: true` in `OAuth2AuthorizationServerProperties`, the authorization server supports multiple issuers under the same host using path-based differentiation. This allows each tenant to have a distinct issuer URL (e.g., `https://auth.example.com/tenant-a` vs `https://auth.example.com/tenant-b`) while sharing the same runtime instance and token validation logic.

### How do I configure a default tenant for single-tenant deployments?

Set the `tenant.default-code` property in your [`application.yml`](https://github.com/koushenhai/kcloud-platform-iot/blob/main/application.yml) to specify the fallback tenant code when operating in single-tenant mode. The system uses this value when no explicit `tenant_code` is provided in the request, allowing the same codebase to function in both multi-tenant and single-tenant configurations without modification.