XM Gate is an API Gateway microservice built with Spring Boot 4.0.1 and Spring Cloud Gateway Server MVC. It serves as the central entry point for the XM Online microservices architecture, providing routing, security, multi-tenancy support, and file handling capabilities.
- Technology Stack
- Features
- Getting Started
- Routes Configuration
- File Upload
- File Download
- Identity Provider (IDP) Configuration
- Multi-Tenancy
- Rate Limiting
- Application Properties
- Building for Production
- Testing
- Docker Support
- Code Quality
| Technology | Version |
|---|---|
| Java | 25 |
| Spring Boot | 4.0.1 |
| Spring Cloud | 2025.1.0 |
| Spring Cloud Gateway Server MVC | 5.0.0 |
| JHipster | 9.0.0-beta.0 |
| Gradle | 9.1.0 |
- API Gateway - Routes requests to backend microservices
- Multi-tenancy - Domain-based tenant resolution
- Service Discovery - Consul integration with no-consul fallback mode
- File Upload - Multipart file upload proxy to microservices
- File Download - Secure file download with configurable path patterns
- IDP Integration - External Identity Provider support (OAuth2/OIDC)
- Access Control - Endpoint-level authorization
- Rate Limiting - Request rate limiting with Bucket4j
- Observability - Metrics, tracing, and logging support
- Java 25
- Gradle 9.1.0 or use the Gradle wrapper (
./gradlew) - Consul (optional, for service discovery)
- Keycloak or other OIDC provider (for authentication)
By default, the application requires Consul for service discovery and configuration.
- Start Consul:
docker compose -f src/main/docker/consul.yml up -d- Run the application:
./gradlew bootRunThe application can run without Consul by using the noconsul profile. This is useful for local development or environments where Consul is not available.
- Activate the
noconsulprofile:
./gradlew bootRun --args='--spring.profiles.active=dev,noconsul'Or set the environment variable:
SPRING_PROFILES_ACTIVE=dev,noconsul ./gradlew bootRun- Configure service instances in
application-noconsul.yml:
spring:
cloud:
consul:
enabled: false
config:
enabled: false
discovery:
enabled: false
loadbalancer:
enabled: true
discovery:
enabled: true
client:
simple:
instances:
config:
- uri: http://localhost:8084
uaa:
- uri: http://localhost:9999
entity:
- uri: http://localhost:8081The no-consul mode uses Spring Cloud's simple discovery client to define static service instances.
Gateway routes are configured in application-route.yml using Spring Cloud Gateway Server MVC YAML configuration.
Routes are defined under spring.cloud.gateway.server.webmvc.routes:
spring:
cloud:
gateway:
server:
webmvc:
routes:
- id: entity
uri: lb://entity
predicates:
- Path=/entity/**
filters:
- StripPrefix=1
- AddDomainRelayHeaders
- AddHighLog
- TenantInit
- AccessControl
- AddRequestHeader=X-Request-service, [ENTITY]Route Properties:
id- Unique identifier for the routeuri- Target service URI (uselb://prefix for load-balanced services)predicates- Conditions for matching requests (e.g.,Path,Method,Header)filters- Request/response transformations
| Filter | Description |
|---|---|
StripPrefix=N |
Removes N path segments from the request |
AddDomainRelayHeaders |
Adds tenant context headers (x-domain, x-tenant, x-scheme, x-port, x-webapp-url) |
AddHighLog |
Enables detailed logging for requests |
TenantInit |
Initializes tenant context from domain mapping |
AccessControl |
Enforces endpoint-level access control |
AddLogging |
Adds request/response logging |
IdpStatefulMode |
Handles IDP stateful authentication mode |
TfaTokenDetection |
Detects two-factor authentication tokens |
AddRequestHeader |
Adds custom headers to requests |
Excluding Services from Routing:
Configure services to exclude from dynamic routing:
application:
gateway:
excluded-services:
- consul
- gateXM Gate provides a dedicated endpoint for proxying multipart file uploads to backend microservices. This avoids memory issues with large file uploads by streaming files directly.
Endpoint: POST /upload/{service}/{path}
Example:
curl -X POST \
http://localhost:8080/upload/entity/api/functions/UPLOAD/execute \
-F "file=@document.pdf"This request proxies the multipart form to the entity service at /api/functions/UPLOAD/execute.
Features:
- Streams files without loading into memory
- Supports POST and PUT methods
- Preserves all form parameters and headers
- Validates service names (alphanumeric only)
- Security validation to prevent path traversal
Implementation: com.icthh.xm.gate.web.rest.file.UploadResource
XM Gate supports secure file downloads with configurable path resolution strategies.
Endpoint: GET /api/download/{recordType}/{fileName}
Example:
curl -X GET http://localhost:8080/api/download/reports/monthly-report.pdf \
-H "Authorization: Bearer <token>"Security: Requires RESOURCE.FILE.DOWNLOAD permission.
Configure download patterns per tenant in /config/tenants/{tenantName}/file-download.yml:
patterns:
- key: date
strategy: XM_TOKEN_MATCHER
pathPrefix: /files/{yyyy}/{mm}/{dd}/
- key: token
strategy: XM_TOKEN_MATCHER
pathPrefix: /{tenant}/{userKey}/{additionalDetails.testParam1}/
- key: mixed
strategy: XM_TOKEN_MATCHER
pathPrefix: /root/{yyyy}/{tenant}/{mm}/{userKey}/
- key: const
strategy: XM_TOKEN_MATCHER
pathPrefix: /static/media/Configuration Properties:
key- Unique identifier used asrecordTypein the download URLstrategy- Path resolution strategy (XM_TOKEN_MATCHER)pathPrefix- Template for file path resolution
The XM_TOKEN_MATCHER strategy supports the following placeholders:
{yyyy},{mm},{dd}- Date components{tenant}- Current tenant key{userKey}- User identifier from JWT token{additionalDetails.*}- Custom claims from JWT token
File Storage Root:
Configure the base path for file storage:
application:
object-storage-file-root: /var/data/filesImplementation: com.icthh.xm.gate.web.rest.file.DownloadResource
XM Gate supports external Identity Providers through OAuth2/OIDC. IDP configuration is tenant-specific and loaded from the config server.
Public Configuration: /config/tenants/{tenant}/idp-public.yml
idp:
config:
features:
# Feature flags for IDP behavior
clients:
- key: google
name: Google
clientId: your-client-id
redirectUri: "{baseUrl}/login/oauth2/code/{registrationId}"
openIdConfig:
authorizationEndpoint:
uri: https://accounts.google.com/o/oauth2/v2/auth
tokenEndpoint:
uri: https://oauth2.googleapis.com/token
grantType: authorization_code
userinfoEndpoint:
uri: https://openidconnect.googleapis.com/v1/userinfo
userNameAttributeName: email
jwksEndpoint:
uri: https://www.googleapis.com/oauth2/v3/certsPrivate Configuration: /config/tenants/{tenant}/idp-private.yml
idp:
config:
clients:
- key: google
clientSecret: your-client-secret
scope:
- openid
- profile
- emailXM Gate provides domain-based multi-tenancy. Tenants are resolved from the request domain.
Tenant Resolution Flow:
- Extract domain from
Hostheader - Look up tenant mapping in configuration
- Set tenant context for the request
- Add
x-tenantheader to downstream requests
Configure Host-to-Tenant Mapping:
application:
hosts:
- localhost
- localTenant Context Headers:
| Header | Description |
|---|---|
x-tenant |
Resolved tenant key |
x-domain |
Request domain |
x-scheme |
Request scheme (http/https) |
x-port |
Request port |
x-webapp-url |
Referer-based webapp URL |
Key application properties in application.yml:
application:
# Hosts for tenant resolution
hosts:
- localhost
- local
# Kafka configuration
kafka-enabled: true
kafka-system-queue: system_queue
# Retry configuration
retry:
max-attempts: 3
delay: 1000
multiplier: 2
# Tenant properties
tenant-properties-path-pattern: /config/tenants/{tenantName}/gate/${application.tenant-properties-name}
tenant-properties-name: gate.yml
tenant-ignored-path-list: /v2/api-docs, /v3/api-docs, /api/profile-info, /management/health
# File storage
object-storage-file-root: /
# IDP settings
disable-idp-cookie-usage: false
redirect-to-default-tenant-enabled: false
# Gateway settings
gateway:
authorized-microservices-endpoints:
entity: /api/**
excluded-services:
- consul
- gate
# XM Config client
xm-config:
enabled: true
xm-config-url: http://config:8084
kafka-config-topic: config_topic
./gradlew -Pprod clean bootJarRun the JAR:
java -jar build/libs/*.jar./gradlew -Pprod -Pwar clean bootWarRun all tests:
./gradlew testRun unit tests only:
./gradlew test --tests '*UnitTest*'Run integration tests only:
./gradlew test --tests '*IntTest*'Run with test coverage:
./gradlew test jacocoTestReport./gradlew bootJar jibDockerBuildOr using npm:
npm run java:dockerFor ARM64 (Apple Silicon):
npm run java:docker:arm64docker compose -f src/main/docker/app.yml up -d| File | Description |
|---|---|
app.yml |
Main application |
consul.yml |
Consul service discovery |
keycloak.yml |
Keycloak identity provider |
monitoring.yml |
Prometheus + Grafana |
sonar.yml |
SonarQube code analysis |
zipkin.yml |
Distributed tracing |
Start SonarQube:
docker compose -f src/main/docker/sonar.yml up -dRun analysis:
./gradlew -Pprod clean check jacocoTestReport sonarqube- Checkstyle - Code style validation
- PMD - Static code analysis
- SpotBugs - Bug pattern detection
Run all checks:
./gradlew checkThis project is part of XM Online platform.