use std::sync::Arc;
use sa_token_core::{
SaTokenConfig, SsoServer, SsoClient, SsoManager, SsoConfig,
};
use sa_token_storage_memory::MemoryStorage;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== sa-token SSO Example ===\n");
let storage = Arc::new(MemoryStorage::new());
let manager = SaTokenConfig::builder()
.storage(storage.clone())
.timeout(7200) .build();
println!("Step 1: Create SSO Server");
let manager = Arc::new(manager);
let sso_server = Arc::new(
SsoServer::new(manager.clone())
.with_ticket_timeout(300) );
println!("SSO Server created with 5-minute ticket timeout\n");
println!("Step 2: Create SSO Clients");
let client1 = Arc::new(SsoClient::new(
manager.clone(),
"http://sso.example.com/auth".to_string(), "http://app1.example.com".to_string(), ));
let client2 = Arc::new(SsoClient::new(
manager.clone(),
"http://sso.example.com/auth".to_string(),
"http://app2.example.com".to_string(),
));
let client3 = Arc::new(SsoClient::new(
manager.clone(),
"http://sso.example.com/auth".to_string(),
"http://app3.example.com".to_string(),
));
println!("Created 3 SSO clients\n");
println!("Step 3: SSO Config");
let sso_config = SsoConfig::builder()
.server_url("http://sso.example.com/auth") .ticket_timeout(300) .allow_cross_domain(true) .add_allowed_origin("http://app1.example.com".to_string()) .add_allowed_origin("http://app2.example.com".to_string())
.add_allowed_origin("http://app3.example.com".to_string())
.build();
let sso_manager = SsoManager::new(sso_config)
.with_server(sso_server.clone())
.with_client(client1.clone());
println!("SSO Manager configured\n");
println!("{}", "=".repeat(60));
println!("\nScenario 1: User logs in at App1");
println!("{}", "-".repeat(60));
let user_id = "user_123";
let ticket1 = sso_server.login(
user_id.to_string(),
"http://app1.example.com".to_string(),
).await?;
println!("Generated ticket for App1:");
println!(" Ticket ID: {}", ticket1.ticket_id);
println!(" Service: {}", ticket1.service);
println!(" Login ID: {}", ticket1.login_id);
println!(" Expires: {}", ticket1.expire_time);
let login_id1 = sso_server.validate_ticket(
&ticket1.ticket_id,
"http://app1.example.com",
).await?;
println!("Ticket validated successfully: {}", login_id1);
let token1 = client1.login_by_ticket(login_id1.clone()).await?;
println!("App1 local session created: {}", token1);
println!("\nScenario 2: User accesses App2 (SSO in action)");
println!("{}", "-".repeat(60));
let is_logged_in = sso_server.is_logged_in(user_id).await;
println!("User already logged in SSO Server: {}", is_logged_in);
let ticket2 = sso_server.create_ticket(
user_id.to_string(),
"http://app2.example.com".to_string(),
).await?;
println!("Generated ticket for App2:");
println!(" Ticket ID: {}", ticket2.ticket_id);
let login_id2 = sso_server.validate_ticket(
&ticket2.ticket_id,
"http://app2.example.com",
).await?;
let token2 = client2.login_by_ticket(login_id2.clone()).await?;
println!("App2 local session created: {}", token2);
println!("\nScenario 3: User accesses App3");
println!("{}", "-".repeat(60));
let ticket3 = sso_server.create_ticket(
user_id.to_string(),
"http://app3.example.com".to_string(),
).await?;
let login_id3 = sso_server.validate_ticket(
&ticket3.ticket_id,
"http://app3.example.com",
).await?;
let token3 = client3.login_by_ticket(login_id3.clone()).await?;
println!("App3 local session created: {}", token3);
println!("\nScenario 4: Check SSO Session");
println!("{}", "-".repeat(60));
let active_clients = sso_server.get_active_clients(user_id).await;
println!("User logged into {} applications:", active_clients.len());
for (i, client) in active_clients.iter().enumerate() {
println!(" {}. {}", i + 1, client);
}
let session = sso_server.get_session(user_id).await;
if let Some(s) = session {
println!("SSO Session details:");
println!(" Login ID: {}", s.login_id);
println!(" Active clients: {}", s.clients.len());
println!(" Created: {}", s.create_time);
println!(" Last active: {}", s.last_active_time);
}
println!("\nScenario 5: Unified Logout");
println!("{}", "-".repeat(60));
let logged_out_clients = sso_server.logout(user_id).await?;
println!("User logged out from SSO Server");
println!("Notifying {} clients to logout:", logged_out_clients.len());
for (i, client_url) in logged_out_clients.iter().enumerate() {
println!(" {}. {} - clearing local session", i + 1, client_url);
}
client1.handle_logout(user_id).await?;
client2.handle_logout(user_id).await?;
client3.handle_logout(user_id).await?;
println!("All local sessions cleared");
let is_still_logged_in = sso_server.check_session(user_id).await;
println!("User still logged in: {}", is_still_logged_in);
println!("\nScenario 6: Ticket Expiration");
println!("{}", "-".repeat(60));
let expired_ticket = sso_server.create_ticket(
"user_456".to_string(),
"http://app1.example.com".to_string(),
).await?;
println!("Created ticket: {}", expired_ticket.ticket_id);
println!("Ticket is valid: {}", expired_ticket.is_valid());
match sso_server.validate_ticket(
&expired_ticket.ticket_id,
"http://app1.example.com",
).await {
Ok(id) => println!("Ticket validated: {}", id),
Err(e) => println!("Ticket validation failed: {}", e),
}
match sso_server.validate_ticket(
&expired_ticket.ticket_id,
"http://app1.example.com",
).await {
Ok(id) => println!("Second validation: {}", id),
Err(e) => println!("Second validation failed (ticket already used): {}", e),
}
println!("\nScenario 7: Cross-Domain Support");
println!("{}", "-".repeat(60));
let origins = vec![
"http://app1.example.com",
"http://app2.example.com",
"http://evil.com",
"http://app3.example.com",
];
for origin in origins {
let allowed = sso_manager.is_allowed_origin(origin);
println!("Origin '{}': {}", origin, if allowed { "✓ Allowed" } else { "✗ Denied" });
}
println!("\nScenario 8: Client URL Generation");
println!("{}", "-".repeat(60));
println!("Client1 Login URL: {}", client1.get_login_url());
println!("Client1 Logout URL: {}", client1.get_logout_url());
println!("Client2 Login URL: {}", client2.get_login_url());
println!("Client3 Login URL: {}", client3.get_login_url());
println!("\nScenario 9: Service Mismatch Protection");
println!("{}", "-".repeat(60));
let ticket_app1 = sso_server.create_ticket(
"user_789".to_string(),
"http://app1.example.com".to_string(),
).await?;
match sso_server.validate_ticket(
&ticket_app1.ticket_id,
"http://app2.example.com",
).await {
Ok(_) => println!("Validation succeeded (unexpected)"),
Err(e) => println!("Service mismatch detected: {}", e),
}
println!("\nScenario 10: Cleanup Expired Tickets");
println!("{}", "-".repeat(60));
for i in 1..=5 {
let _ = sso_server.create_ticket(
format!("temp_user_{}", i),
"http://app1.example.com".to_string(),
).await;
}
println!("Created 5 temporary tickets");
sso_server.cleanup_expired_tickets().await;
println!("Cleanup completed - all valid tickets remain");
println!("\n{}", "=".repeat(60));
println!("SSO Example completed successfully!");
println!("Key Features Demonstrated:");
println!(" ✓ Single Sign-On across multiple applications");
println!(" ✓ Ticket-based authentication");
println!(" ✓ Unified logout");
println!(" ✓ Cross-domain support with origin validation");
println!(" ✓ Ticket expiration and reuse prevention");
println!(" ✓ Service mismatch protection");
println!(" ✓ Session management");
Ok(())
}