Distributed Session Management
English
Overview
The Distributed Session Management module enables session sharing across multiple microservices. It provides service authentication, cross-service session access, and attribute management with automatic timeout handling.
This module is designed for microservices architectures where multiple services need to share user authentication state and session data seamlessly.
Architecture
┌────────────────────────────────────────────────────────────────────┐
│ Microservices Architecture │
│ 微服务架构 │
└────────────────────────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Service A │ │ Service B │ │ Service C │
│ (User API) │ │ (Order API) │ │ (Pay API) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└──────────────────┼──────────────────┘
│
┌─────────▼──────────┐
│ Distributed │
│ Session Storage │
│ (Redis/Database) │
└────────────────────┘
Each service can:
- Create sessions for users
- Access sessions created by other services
- Share user authentication stateKey Features
- Cross-Service Session Sharing - Share sessions across microservices
- Service Authentication - Verify service credentials with secret keys
- Session Attributes - Store custom key-value pairs for user context
- Multi-Session Support - One user can have multiple sessions (multi-device)
- Automatic Cleanup - TTL-based session expiration
- Pluggable Storage - Use custom storage backends (Redis, Database, Memory)
- Permission-Based Access - Fine-grained control via service permissions
- Session Monitoring - Track all active sessions per user
Quick Start
use sa_token_core::{
DistributedSessionManager, InMemoryDistributedStorage, ServiceCredential
};
use std::sync::Arc;
use std::time::Duration;
use chrono::Utc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create distributed session manager
let storage = Arc::new(InMemoryDistributedStorage::new());
let manager = DistributedSessionManager::new(
storage,
"service-main".to_string(),
Duration::from_secs(3600), // 1 hour TTL
);
// Register a service
let credential = ServiceCredential {
service_id: "api-gateway".to_string(),
service_name: "API Gateway".to_string(),
secret_key: "secret123".to_string(),
created_at: Utc::now(),
permissions: vec!["read".to_string(), "write".to_string()],
};
manager.register_service(credential).await;
// Verify service
let verified = manager.verify_service("api-gateway", "secret123").await?;
println!("Service verified: {}", verified.service_name);
// Create session
let session = manager.create_session(
"user123".to_string(),
"token456".to_string(),
).await?;
// Set session attribute
manager.set_attribute(
&session.session_id,
"role".to_string(),
"admin".to_string(),
).await?;
// Get session attribute
if let Some(role) = manager.get_attribute(&session.session_id, "role").await? {
println!("User role: {}", role);
}
// Get all sessions for user
let sessions = manager.get_sessions_by_login_id("user123").await?;
println!("User has {} active sessions", sessions.len());
Ok(())
}Service Authentication Flow
Service A Manager Service B
| | |
|-- register_service ------>| |
|<----- registered ---------| |
| | |
| |<-- verify_service(id, secret)
| |--- check credentials ---->|
| |<----- verified ----------|Cross-Service Session Access
Service A creates session:
session_id: "uuid-123"
login_id: "user123"
attributes: {"role": "admin"}
Service B accesses session:
get_session("uuid-123") -> Full session data
Can read/modify attributes
Updates last_access timestampAPI Reference
DistributedSessionManager
Methods:
new(storage, service_id, timeout)- Create managerregister_service(credential)- Register a serviceverify_service(id, secret)- Verify service credentialscreate_session(login_id, token)- Create new sessionget_session(session_id)- Get session by IDupdate_session(session)- Update existing sessiondelete_session(session_id)- Delete sessionset_attribute(id, key, value)- Set session attributeget_attribute(id, key)- Get session attributeremove_attribute(id, key)- Remove session attributeget_sessions_by_login_id(login_id)- Get all user sessionsdelete_all_sessions(login_id)- Delete all user sessions
Use Cases
1. Single Sign-On (SSO) Across Services
Users log in once and access multiple services without re-authentication:
User → Service A: Login
├─ Create session: session_id = "abc123"
└─ Save to distributed storage
User → Service B: Request with session_id = "abc123"
├─ Service B retrieves session from storage
├─ Validates user is authenticated
└─ Processes request ✅ (No re-login needed!)2. Session Sharing for User Context
Services share user context and state:
Service A stores: { "user_role": "admin", "department": "IT" }
Service B reads: Same session attributes available
Service C updates: { "last_order": "order_123" }
→ All services share the same session state!3. Multi-Device Session Management
One user can have multiple active sessions:
User: user_123
├─ Session 1: Web (Service A)
├─ Session 2: Mobile (Service B)
└─ Session 3: Desktop (Service C)
All sessions can be:
- Listed: get_sessions_by_login_id()
- Managed individually
- Terminated all at once: delete_all_sessions()4. Microservices Architecture
Share user sessions across API Gateway, User Service, Order Service, etc.
5. Multi-Region Deployment
Synchronize sessions across different geographic regions using shared storage.
6. Load Balancing
Maintain session consistency across multiple server instances.
Storage Backends
Redis Implementation (Recommended)
use redis::AsyncCommands;
pub struct RedisDistributedStorage {
client: redis::Client,
}
#[async_trait]
impl DistributedSessionStorage for RedisDistributedStorage {
async fn save_session(&self, session: DistributedSession, ttl: Option<Duration>)
-> Result<(), SaTokenError>
{
let mut conn = self.client.get_async_connection().await?;
let key = format!("distributed:session:{}", session.session_id);
let value = serde_json::to_string(&session)?;
if let Some(ttl) = ttl {
conn.set_ex(&key, value, ttl.as_secs() as usize).await?;
} else {
conn.set(&key, value).await?;
}
// Index by login_id for quick lookup
let index_key = format!("distributed:login:{}", session.login_id);
conn.sadd(index_key, &session.session_id).await?;
Ok(())
}
// ... implement other methods
}Database Implementation
use sqlx::PgPool;
pub struct PostgresDistributedStorage {
pool: PgPool,
}
#[async_trait]
impl DistributedSessionStorage for PostgresDistributedStorage {
async fn save_session(&self, session: DistributedSession, ttl: Option<Duration>)
-> Result<(), SaTokenError>
{
let expires_at = ttl.map(|t| Utc::now() + chrono::Duration::from_std(t).unwrap());
sqlx::query!(
"INSERT INTO distributed_sessions
(session_id, login_id, token, service_id, attributes, expires_at)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (session_id) DO UPDATE
SET attributes = $5, last_access = NOW()",
session.session_id,
session.login_id,
session.token,
session.service_id,
serde_json::to_value(&session.attributes)?,
expires_at,
)
.execute(&self.pool)
.await?;
Ok(())
}
// ... implement other methods
}Best Practices
1. Service Registration
Use crypto-secure secret generation for service credentials:
let credential = ServiceCredential {
service_id: "user-service".to_string(),
service_name: "User Management Service".to_string(),
secret_key: generate_secure_secret(), // Use crypto-secure generation
created_at: Utc::now(),
permissions: vec!["user.read".to_string(), "user.write".to_string()],
};
manager.register_service(credential).await;2. Session Creation with Context
Add relevant attributes immediately after session creation:
let session = manager.create_session(login_id, token).await?;
// Add relevant attributes immediately
manager.set_attribute(&session.session_id, "user_role".to_string(), "admin".to_string()).await?;
manager.set_attribute(&session.session_id, "department".to_string(), "IT".to_string()).await?;
manager.set_attribute(&session.session_id, "login_device".to_string(), "web".to_string()).await?;3. Cross-Service Access Pattern
Always verify service identity and check permissions:
// 1. Verify service identity
let service_cred = manager.verify_service("service-b", request.secret).await?;
// 2. Check permissions
if !service_cred.permissions.contains(&"session.read".to_string()) {
return Err(SaTokenError::PermissionDenied);
}
// 3. Access session
let session = manager.get_session(&request.session_id).await?;
// 4. Refresh to keep session alive
manager.refresh_session(&session.session_id).await?;4. Multi-Device Logout
Support both individual and bulk logout:
// Logout from all devices
manager.delete_all_sessions(&login_id).await?;
// Or logout specific session
manager.delete_session(&session_id).await?;5. Session Monitoring
Monitor user's active sessions for security:
let sessions = manager.get_sessions_by_login_id(&login_id).await?;
for session in sessions {
println!("Session: {} from service: {}, last active: {}",
session.session_id,
session.service_id,
session.last_access
);
// Check for suspicious activity
if is_suspicious(&session) {
manager.delete_session(&session.session_id).await?;
}
}6. Security Considerations
- ✅ Service Authentication: Each service has unique secret_key
- ✅ Permission-Based Access: Services have explicit permissions
- ✅ Session Timeout: Configure appropriate TTL
- ✅ Data Encryption: Encrypt sensitive session attributes
- ✅ Audit Logging: Log session creation/deletion and cross-service access
7. Production Recommendations
- Use appropriate TTL - Set session timeout based on security requirements (typically 1-24 hours)
- Use persistent storage - Implement Redis/Database storage for production (not in-memory)
- Secure service credentials - Use strong secret keys and rotate periodically
- Monitor session count - Track active sessions per user to detect anomalies
- Implement cleanup - Use storage TTL features for automatic cleanup
- Enable encryption - Encrypt sensitive session attributes at rest
Related Documentation
License
MIT OR Apache-2.0