Licences flottantes vs par poste : architecture et implémentation

LicensingPython
← Tous les articles

Deux modèles de licensing

Il existe deux grandes familles de licences logicielles professionnelles : les licences par poste (named user / per seat) qui lient une licence à une machine ou un utilisateur précis, et les licences flottantes (concurrent / floating) qui autorisent N utilisateurs simultanés parmi un plus grand groupe.

Une organisation de 100 personnes dont 20 utilisent le logiciel en même temps peut acheter 20 licences flottantes plutôt que 100 licences par poste — économie de 80%. C'est le cas d'usage central des licences flottantes.

Licences par poste

La licence par poste est le modèle d'IronLock par défaut. Elle est liée au fingerprint hardware d'une machine précise. Simple à implémenter, simple à auditer, mais rigide : si un utilisateur change de poste, la licence doit être re-générée.

Licences flottantes

Les licences flottantes nécessitent un serveur central (licence server) qui gère un pool de N slots disponibles. Chaque client checke out un slot au démarrage et le restitue à la fermeture.

Checkout / Checkin

# Serveur de licences flottantes (FastAPI)
from fastapi import FastAPI, HTTPException
import asyncio
from datetime import datetime, timezone, timedelta

app = FastAPI()
POOL_SIZE = 20  # 20 licences flottantes

# sessions = {session_id: {user, machine, expires_at}}
sessions: dict = {}
lock = asyncio.Lock()

@app.post("/checkout")
async def checkout(user: str, machine_id: str):
    async with lock:
        # Nettoyer les sessions expirées
        now = datetime.now(timezone.utc)
        expired = [sid for sid, s in sessions.items()
                   if s['expires_at'] < now]
        for sid in expired: del sessions[sid]

        if len(sessions) >= POOL_SIZE:
            raise HTTPException(429, f"Pool plein ({POOL_SIZE}/{POOL_SIZE} licences utilisées)")

        session_id = generate_token()
        sessions[session_id] = {
            'user': user, 'machine': machine_id,
            'expires_at': now + timedelta(minutes=30)
        }
        return {"session_id": session_id, "pool_used": len(sessions)}

@app.post("/checkin")
async def checkin(session_id: str):
    async with lock:
        sessions.pop(session_id, None)
    return {"released": True}

Timeout et heartbeat

Un client qui plante sans faire de checkin bloquerait un slot indéfiniment. Le heartbeat résout ce problème : le client envoie un signal régulier, et les sessions sans heartbeat expirent automatiquement :

@app.post("/heartbeat")
async def heartbeat(session_id: str):
    async with lock:
        if session_id not in sessions:
            raise HTTPException(404, "Session expirée")
        sessions[session_id]['expires_at'] = (
            datetime.now(timezone.utc) + timedelta(minutes=30)
        )
    return {"renewed": True}

# Côté client — heartbeat toutes les 10 minutes
import threading
def heartbeat_loop(session_id, url):
    while True:
        threading.Event().wait(600)  # 10 min
        requests.post(f"{url}/heartbeat", json={"session_id": session_id})

Haute disponibilité

Pour éviter qu'une panne du serveur de licences bloque tous les utilisateurs, deux approches :

Quand choisir quoi ?

CritèreFlottantPar poste
Utilisation simultanée faible✓ ÉconomiqueCoûteux
Fonctionnement offline requis✗ Serveur nécessaire✓ Natif
Utilisateurs nomades✓ FlexibleRebind requis
Simplicité d'implémentationModéré✓ Simple
Audit d'utilisation précis✓ Logs serveurEstimé

Conclusion

Les licences flottantes sont idéales pour les organisations avec un taux d'utilisation faible (20-40% des utilisateurs actifs simultanément). Elles nécessitent un serveur de licences — mais ce serveur peut être léger (FastAPI + SQLite suffit pour 500 clients). IronLock v2 supporte les deux modèles : le mode par poste nativement, le mode flottant via un serveur de licences IronLock dédié.

🔐
PRODUIT LIÉ
IronLock v2.0
Découvrir IronLock → Manuel complet
← Article précédent Article suivant →