Sitemap

The Journey of Enterprise SSO Implementation | Part 1: Migration

8 min readAug 14, 2025
Press enter or click to view image in full size

This article recounts our journey in building and migrating an enterprise Single Sign-On (SSO) system using Java Spring Boot with various modern patterns and technologies.

Baca dalam Bahasa Indonesia

🎯 Background

One of the largest institutions in Indonesia faced a major challenge in managing user access and integration with various systems. Previously, each system had a centralized authentication mechanism with separate data sources, which led to:

  • Fragmented Database: Databases for several user levels were separate, sometimes causing data redundancy and inconsistencies in user profiles. Currently, there are at least more than 80,000 users with 30,000 active users spread across 2 different data sources.
  • Security Risks: The more login systems there are, the more potential security vulnerabilities. The absence of a standard refresh token scheme makes access tokens vulnerable to misuse.
  • Complex Access Rights Management: It was difficult to implement and manage uniform security policies and access rights across all applications.
  • No Authentication Standard: The lack of a centralized system hindered integration with modern authentication protocols like SAML or OAuth2/OIDC.
  • and others…

🔄 SSO Technology Evolution

The SSO technology journey at our institution did not happen overnight. Our developer team has gone through several interesting phases of technological evolution:

Phase 1: PHP — Go (2022–Present)

  • Using Go for performance improvement
  • Implementation of microservices architecture
  • Better concurrency handling with goroutines
  • However, there were still challenges in distributed data management due to separate data sources and no authentication standard

Phase 2: PHP-Java (Present — Next Few Months)

  • We are using Java because the majority of backend applications use Java with Spring Boot.
  • Java Spring Boot as a Migration Service: Manages data migration, user sync, and complex business logic
  • Distributed Architecture: Redis caching, circuit breakers, and async processing
  • The question is, where is this migration headed?

🔍 Identity Provider Research & Technology Decision

After analyzing various identity provider solutions on the market, we conducted a deep evaluation of several main options:

Identity Providers Evaluated:

Commercial Solutions:

  • Auth0: Enterprise-grade with complete features, but a complex pricing model for our scale.
  • Okta: Market leader with enterprise features, but overkill for our internal needs.
  • Azure AD: Microsoft ecosystem integration, but lock-in with Microsoft and licensing complexity.
  • AWS Cognito: Cloud-native solution, but dependency on AWS and potential vendor lock-in.

Open Source Solutions:

  • Keycloak: Open-source, self-hosted, enterprise features without vendor lock-in.
  • Authentik: Modern open-source IDP with a user-friendly UI, but still relatively new with a smaller community.
  • Casdoor: Lightweight open-source solution, good for simple use cases but lacks enterprise features.
  • WSO2 Identity Server: Enterprise-grade open source with complete features, but high complexity and resource-intensive.
  • CAS (Central Authentication Service): Mature open-source solution from Apereo, good for institutions but UI/UX is less modern.
  • Gluu: Open-source identity platform with a strong security focus, but high deployment complexity.
  • ORY Hydra: Cloud-native OAuth 2.0 & OpenID Connect server, good for microservices but lacks enterprise features.

And we chose Keycloak.

Why Choose Keycloak?

After months of research and proof-of-concept on 7+ IDP solutions, Keycloak became the top choice for several strategic reasons:

  • Open Source & Self-Hosted: Full control over data and infrastructure, no dependency on external vendors.
  • Enterprise Features: Support for SAML, OAuth 2.0, OpenID Connect, LDAP federation, and multi-factor authentication.
  • Cost-Effective: No licensing fees, only operational costs for infrastructure.
  • Community & Support: Large community, extensive documentation, and enterprise support from Red Hat.
  • Integration Flexibility: Easy to integrate with various legacy systems through multiple protocols.
  • Scalability: Architecture designed to handle an enterprise-scale user base.
  • Maturity & Stability: Already production-ready with a proven track record in enterprise environments.
  • Vendor Lock-in: Some open-source IDPs have their own libraries to use their systems, and I saw this as inconsistent with duplications that do not comply with authentication standards like OpenID and SAML.
  • Indonesian Community: Many institutions are already using Keycloak.

Migration Strategy & Implementation

The implementation of Keycloak was done with a gradual migration approach that minimized disruption:

  • Phase 1: Running user migration by creating a migration service with Java Spring Boot similar to the old SSO, so all apps can run normally without downtime.
  • Phase 2: Migration of backend, frontend, or full-stack applications.
  • Phase 3: Management of service accounts for inter-application communication.
  • Phase 4: Centralized access control management for all applications.

🏗️ System Architecture

High-Level Architecture

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│ Web Client │────│ SSO BE │────│ Keycloak │
│ │ │ (Spring Boot) │ │ (ID Provider) │
└─────────────────┘ └─────────────────┘ └─────────────────┘


┌─────────────────┐
│ Redis Cache │
│ (Distributed) │
└─────────────────┘

Core Components

  • Authentication Service — Handles login/logout logic
  • JWT Token Service — Generates and validates JWT tokens
  • Keycloak Integration — Syncs with Keycloak
  • Distributed Cache Service — Redis-based caching
  • Circuit Breaker — Fault tolerance mechanism

🚀 Engineering Patterns & Technologies

1. Distributed Caching with Redis

A distributed caching system is key to the performance of the enterprise SSO migration system. We use Redis as a cache layer with several strategies:

@Service
public class DistributedCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void setWithExpiry(String key, Object value, Duration expiry) {
redisTemplate.opsForValue().set(key, value, expiry);
}
public <T> T get(String key, Class<T> clazz) {
Object value = redisTemplate.opsForValue().get(key);
return clazz.cast(value);
}
}

Benefits:

  • Performance: Response time reduced from 500ms → ~20ms
  • Scalability: Cache can be shared between instances
  • Consistency: TTL-based expiration for data freshness

2. Asynchronous Processing with CompletableFuture

To handle multiple API calls concurrently without needing a response, we implemented the CompletableFuture pattern:

@Service
public class AuthenticationService {
public CompletableFuture<AuthenticationResponse> authenticateAsync(
AuthenticationRequest request) {
return CompletableFuture.supplyAsync(() -> {
// Validate credentials
User user = validateCredentials(request);
// Generate JWT token
String token = jwtTokenService.generateToken(user);
// Update last login
updateLastLoginAsync(user.getId());
return new AuthenticationResponse(token, user);
});
}
private CompletableFuture<Void> updateLastLoginAsync(Long userId) {
return CompletableFuture.runAsync(() -> {
// Async database update
userRepository.updateLastLogin(userId, LocalDateTime.now());
});
}
}

Advantages:

  • Non-blocking: Users don’t have to wait for non-critical operations
  • Better UX: Faster response time
  • Resource Efficiency: Optimal utilization of the thread pool

3. Circuit Breaker Pattern

Implementation of Resilience4j to handle failure gracefully:

@Configuration
public class CustomCircuitBreakerConfig {
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 50% failure rate
.waitDurationInOpenState(Duration.ofSeconds(60))
.slidingWindowSize(10) // 10 calls to calculate failure rate
.build();
return CircuitBreakerRegistry.of(config);
}
}
@Service
public class KeycloakAuthService {
@CircuitBreaker(name = "keycloakAuth", fallbackMethod = "fallbackAuth")
public AuthResult authenticateWithKeycloak(String username, String password) {
// Call Keycloak API
return keycloakClient.authenticate(username, password);
}
public AuthResult fallbackAuth(String username, String password, Exception ex) {
// Fallback to local authentication
log.warn("Keycloak unavailable, using fallback: {}", ex.getMessage());
return localAuthService.authenticate(username, password);
}
}

Circuit Breaker States:

  • CLOSED: Normal operation
  • OPEN: Service unavailable, return fallback
  • HALF_OPEN: Testing service recovery

📊 Performance & Load Testing

We used k6 for load testing with a realistic scenario for an enterprise SSO:

import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 200 }, // Ramp up to 200 users
{ duration: '2m', target: 1000 }, // Ramp up to 1000 users
{ duration: '2m', target: 3000 }, // Ramp up to 3000 users
{ duration: '5m', target: 5000 }, // Ramp up to 5000 users
{ duration: '2m', target: 5000 }, // Hold at 5000 users
{ duration: '2m', target: 0 }, // Ramp down to 0 users
],
thresholds: {
http_req_duration: ['p(95)<1000'], // 95% of requests < 1 second
http_req_failed: ['rate<0.01'], // Error rate < 1%
},
};
export default function() {
const loginPayload = JSON.stringify({
username: 'test@something.id',
password: 'password123'
});
const params = {
headers: {
'Content-Type': 'application/json',
},
};
const response = http.post('/api/auth/login', loginPayload, params);
check(response, {
'is status 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}

🎯 Real Performance Results from PHP-Java SSO

Test Configuration:

  • Total Duration: 14 minutes (14m00.0s)
  • Target VUs: 5000 virtual users
  • Actual VUs: 9 active users (0.18% VU efficiency)
  • Test Status: Completed successfully

Performance Metrics:

System VU Efficiency P95 Latency Error Rate Overall Score PHP-Java SSO 0.18% 11.37s 2.22% Best PHP-Go SSO 0.20% 13.03s 2.67% Runner-up

Detailed Analysis:

Latency Performance

  • Target: P95 < 1 second
  • Actual: P95 = 11.37 seconds (11.37x slower than target)
  • Distribution:
  • Fastest: 6.75ms
  • Slowest: 1 minute (timeout)
  • 16% of requests met the 500ms target

Error Handling

  • Login Success Rate: 99% (231,064/231,374)
  • Token Generation: 99% success rate
  • HTTP Error Rate: 2.22% (acceptable for a PHP system)
  • Check Failures: 41.91% (mostly related to latency)

System Capacity

  • Target Capacity: 5000 concurrent users
  • Actual Capacity: 9 concurrent users (0.18% efficiency)
  • Bottleneck: System resource limitations, not application logic

🛡️ Security Implementation

JWT Token Security

@Component
public class JwtTokenService {
private static final String SECRET_KEY = "sso-secret-key-2024";
private static final long EXPIRATION_TIME = 3600000; // 1 hour
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getRoles())
.claim("department", user.getDepartment())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.ALGORITHM, SECRET_KEY)
.compact();
}
}

🚀 Deployment & DevOps

Docker Containerization

FROM openjdk:17-jre-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Multi-Container Deployment

# stack-scaled-example.yml
version: '3.8'
services:
sso-app:
image: sso:latest
deploy:
replicas: 3
resources:
limits:
cpus: '1.0'
memory: 1G
environment:
- SPRING_PROFILES_ACTIVE=prod
- REDIS_HOST=redis
redis:
image: redis:7-alpine
volumes:
- redis-data:/data

💡 Lessons Learned

Technical Insights

  • Cache Strategy: Redis cluster with proper TTL management for optimal performance.
  • Async Processing: CompletableFuture for non-blocking operations and better resource utilization.
  • Resilience: Circuit breaker pattern for fault tolerance and graceful degradation.

Business Impact

  • User Experience: Login time reduced by 80% thanks to distributed caching and async processing.
  • Security: Centralized identity management with Keycloak integration (soon).
  • Maintenance: 60% reduction in user management overhead through automation.
  • Scalability: Support for 10,000+ concurrent users with a distributed architecture.
  • Migration Success: Seamless transition from legacy systems to modern SSO without disruption.

Migration Service Insights

  • Dual Authentication: Support for legacy and Keycloak auth in parallel.
  • Data Synchronization: Real-time sync between existing user databases and Keycloak.
  • Rollback Capability: Safe migration with the ability to roll back if necessary.
  • Gradual Migration: Phased user migration to minimize business impact.

🎯 Conclusion

This SSO Migrator project proves that with the right engineering practices, we can build a robust, scalable, and maintainable enterprise system. The main goal of this project is dual-purpose: not only as an SSO system but also as a seamless migration service to Keycloak.

Dual Mission Achievement

🎯 SSO System:

  • Distributed caching with Redis for optimal performance
  • Asynchronous processing with CompletableFuture for scalability
  • Circuit breaker pattern for fault tolerance
  • Comprehensive monitoring and health checks

Migration Service:

  • Hybrid authentication (legacy + Keycloak) for a smooth transition
  • Real-time data synchronization between existing systems and Keycloak
  • Gradual migration strategy to minimize business disruption
  • Rollback capability for risk mitigation

Engineering Excellence

The developer team successfully overcame complex challenges with a systematic and modern approach:

  • Architecture Design: A hybrid PHP-Java approach provided optimal flexibility.
  • Migration Strategy: A 4-phase approach that is safe and controlled.
  • Performance Optimization: Redis caching, async processing, and circuit breakers.

Strategic Impact

This solution provides a solid foundation for digital transformation:

  • Immediate: A unified authentication experience for all users.
  • Short-term: Seamless migration to modern identity management.
  • Long-term: A scalable platform for future apps.

The combination of distributed caching, asynchronous processing, the circuit breaker pattern, and comprehensive monitoring results in an SSO solution that not only meets current needs but is also ready for future growth. The integrated migration service ensures a smooth and safe transition to Keycloak, providing a competitive advantage in the digital transformation journey.

This article was written and does not represent the developer team.

--

--

Fikih Firmansyah
Fikih Firmansyah

Written by Fikih Firmansyah

Back End Developer at Universitas Sumatera Utara || Google Developer Groups Medan

No responses yet