🗺️ Guide des Flux Architecturaux (Enterprise Edition)
Ce document détaille les mécanismes internes de ScribeJava v9.4.x, illustrant comment le module core et le module oidc collaborent pour gérer le cycle de vie des identités.
1. Flux de Connexion OIDC (Login)
Ce flux intègre la Découverte Dynamique, le PKCE et la Validation Native de l'ID Token.
sequenceDiagram
participant App as "Application Client"
participant Disc as OidcDiscoveryService
participant Service as OidcService
participant IdP as "Fournisseur d'Identité (Google/MS)"
Note over App, IdP: 1. PHASE DE DÉCOUVERTE (Discovery)
App->>Disc: getProviderMetadata()
Disc->>IdP: GET /.well-known/openid-configuration
IdP-->>Disc: JSON Metadata (Endpoints, Keys URI)
Disc-->>App: OidcProviderMetadata
Note over App, IdP: 2. PHASE D'AUTORISATION (PKCE)
App->>Service: createAuthorizationUrlBuilder().initPKCE()
Service-->>App: PKCE (Verifier + Challenge)
App->>IdP: Redirection Navigateur (URL avec code_challenge)
IdP-->>App: Redirection avec ?code=XYZ
Note over App, IdP: 3. ÉCHANGE ET VALIDATION
App->>Service: getAccessToken(AuthorizationCodeGrant + Verifier)
Service->>IdP: POST /token (code + code_verifier)
IdP-->>Service: OAuth2AccessToken (incluant ID Token)
Service->>App: token
App->>Service: validateIdToken(token, nonce)
Service->>Service: IdTokenValidator.validate()
Service-->>App: IdToken (Claims décodés)
Classes Clés :
-
OidcDiscoveryService: Gère l'appel au point de terminaison de découverte. -
AuthorizationUrlBuilder: Orchestre la construction de l'URL et l'initialisation duPKCE. -
AuthorizationCodeGrant: Stratégie d'échange du code. -
IdTokenValidator: Valide la signature cryptographique (RSA/EC) et les claims (iss,aud,exp).
🔍 Description Détaillée et Échange de Données du Flux de Connexion (Login)
Voici l'explication pas-à-pas des requêtes et des paramètres échangés :
Étape 1 : Phase de Découverte Dynamique (OIDC Discovery)
L'application interroge le point d'entrée standard de l'IdP pour configurer automatiquement les endpoints.
* Données envoyées : Requête HTTP GET vers l'URL d'auto-configuration :
OidcProviderMetadata) contenant les capacités et les URL de l'IdP :
* issuer : L'identifiant unique de l'émetteur des jetons.
* authorization_endpoint : URL pour rediriger l'utilisateur afin de l'authentifier.
* token_endpoint : URL pour échanger le code contre les jetons.
* jwks_uri : URL publique contenant la liste des clés publiques (JWKS) servant à valider la signature des ID Tokens.
* response_types_supported : Ex. ["code", "token", "id_token"].
* subject_types_supported : Ex. ["public", "pairwise"].
* id_token_signing_alg_values_supported : Les algorithmes de signature supportés (ex. RS256, ES256).
Étape 2 : Phase de Demande d'Autorisation (Redirection PKCE / CSRF)
L'application construit l'URL de redirection de l'utilisateur pour collecter son consentement. ScribeJava génère localement un code_verifier (une chaîne aléatoire cryptographique) et calcule le code_challenge correspondant.
* Données envoyées (Paramètres de l'URL d'Autorisation) :
* response_type=code : Demande un code d'autorisation (Authorization Code Flow).
* client_id : Identifiant de votre application enregistré auprès de l'IdP.
* redirect_uri : L'URL de callback de votre application où l'utilisateur sera redirigé après consentement.
* scope : Doit obligatoirement inclure openid pour activer OIDC (et optionnellement profile, email).
* state : Chaîne de caractères aléatoire unique servant à valider le callback et empêcher les attaques CSRF.
* nonce : Chaîne aléatoire liée à la session pour prévenir les attaques par rejeu de l'ID Token.
* code_challenge : Challenge cryptographique dérivé du code_verifier (ex. hash SHA-256 encodé en Base64URL).
* code_challenge_method=S256 : Algorithme utilisé pour le challenge.
* Données reçues lors du Callback : Le navigateur est redirigé vers l'URL de callback avec :
* code : Le code d'autorisation temporaire (usage unique, durée de vie courte).
* state : Doit être identique à la valeur envoyée au début du flux (validation CSRF).
Étape 3 : Échange du Code contre les Jetons (Token Request)
L'application transmet en direct (Back-Channel) le code d'autorisation et le secret de vérification PKCE à l'IdP pour obtenir les jetons.
* Données envoyées (Requête POST au token_endpoint) :
grant_type=authorization_code : Spécifie le flux d'échange.
* code : Le code d'autorisation reçu à l'étape précédente.
* redirect_uri : Doit être identique à l'URL envoyée à l'étape 2.
* client_id : Identifiant de votre application.
* client_secret : Secret de l'application (requis pour les clients confidentiels).
* code_verifier : Le secret original en clair généré à l'étape 2. L'IdP le hache en SHA-256 et vérifie qu'il correspond au code_challenge de l'étape 2.
* Données reçues (Réponse JSON de l'IdP) :
* access_token : Jeton d'accès permettant d'appeler l'API de l'IdP (ex. UserInfo).
* token_type : Généralement Bearer.
* expires_in : Durée de vie du jeton d'accès (en secondes).
* refresh_token : (Optionnel) Permet de renouveler les jetons d'accès expirés en arrière-plan.
* id_token : Le jeton d'identité OIDC sous forme de JWT (JSON Web Token) signé.
Étape 4 : Validation de l'ID Token
ScribeJava valide localement et de manière stricte le jeton d'identité reçu dans la réponse de l'étape 3 avant de faire confiance aux données utilisateur.
* Validation de l'Enveloppe (JWT Header) :
* Récupération du paramètre kid (Key ID) et de l'algorithme alg (ex. RS256).
* Récupération de la clé publique de signature correspondante via le cache JWKS (jwks_uri).
* Validation de la Signature :
* Vérification de l'intégrité cryptographique du jeton grâce à la clé publique récupérée.
* Validation du Payload (Claims) :
* iss (Issuer) : Doit correspondre exactement à l'émetteur configuré ou découvert.
* aud (Audience) : Doit contenir le client_id de votre application.
* exp (Expiration) : La date actuelle doit être inférieure à la date d'expiration (en tenant compte d'une tolérance d'horloge).
* nonce : Crucial : Doit correspondre exactement au nonce aléatoire généré localement lors de l'Étape 2 pour empêcher un pirate de réinjecter un ID Token intercepté.
* Extraction des Données utilisateur (Standard Claims) :
Une fois validé, le développeur a accès à l'identité certifiée :
* sub : L'identifiant unique et immuable de l'utilisateur.
* email : Adresse e-mail.
* email_verified : Indique si l'e-mail a été vérifié par l'IdP.
* name, given_name, family_name, picture ...
2. Flux de Déconnexion (Logout)
ScribeJava supporte la déconnexion hybride : technique (révocation) et utilisateur (fin de session).
sequenceDiagram
participant App as "Application Client"
participant Service as OidcService
participant IdP as "Fournisseur d'Identité"
Note over App, IdP: MÉTHODE 1 : RÉVOCATION TECHNIQUE (Back-Channel)
App->>Service: revokeToken(accessToken, TokenTypeHint.ACCESS_TOKEN)
Service->>IdP: POST /revocation (token + client_auth)
IdP-->>Service: 200 OK (Jeton invalidé)
Note over App, IdP: MÉTHODE 2 : FIN DE SESSION (Front-Channel)
App->>App: OidcSessionHelper.getLogoutUrl()
App->>IdP: Redirection Navigateur (end_session_endpoint)
IdP-->>App: Redirection vers post_logout_redirect_uri
Points de terminaison impliqués :
-
Revocation Endpoint : Défini par
metadata.getRevocationEndpoint(). -
End Session Endpoint : URL vers laquelle l'utilisateur est redirigé pour le Logout SSO.
3. Flux de Renouvellement (Refresh Token)
Gestion de la persistance sans intervention de l'utilisateur.
sequenceDiagram
participant App as "Application Client"
participant Service as OidcService
participant Grant as RefreshTokenGrant
participant IdP as "Fournisseur d'Identité"
App->>Service: refreshAccessToken(refreshToken)
Service->>Grant: createRequest(service)
Grant->>IdP: POST /token (grant_type=refresh_token)
IdP-->>Service: New OAuth2AccessToken
Service-->>App: token
Classes Clés :
-
RefreshTokenGrant: Encapsule les paramètresrefresh_tokenetclient_id/secret. -
OAuth2AccessToken: Contient le nouveau jeton et sa date d'expiration calculée viagetExpiresAt().
4. Orchestration Enterprise (Integration Helpers)
Le module integration-helpers automatise la gestion du cycle de vie des jetons et la sécurité CSRF, évitant ainsi au développeur de manipuler manuellement les jetons.
4.1. Fin de Flux Orchestrée (Callback)
Le OidcAuthFlowCoordinator centralise toutes les validations de retour.
sequenceDiagram
participant Browser as "Navigateur"
participant App as "Contrôleur Applicatif"
participant Coord as OidcAuthFlowCoordinator
participant Service as OidcService
participant Repo as TokenRepository
participant IdP as "Fournisseur d'Identité"
Browser->>App: GET /callback?code=XYZ&state=ABC
App->>Coord: finishAuthorization(userId, code, state, context)
rect rgb(240, 240, 240)
Note over Coord: 1. Validation CSRF Automatique
Coord->>Coord: Validation de l'état (state)
end
Coord->>Service: getAccessToken(Grant + PKCE Verifier)
Service->>IdP: POST /token
IdP-->>Service: OAuth2AccessToken
rect rgb(240, 240, 240)
Note over Coord: 2. Validation OIDC Automatique
Coord->>Service: validateIdToken(token, nonce)
Service-->>Coord: IdToken
end
Coord->>Repo: save(userId, ExpiringTokenWrapper)
Coord-->>App: OidcAuthResult (Token + Claims consolidés)
App-->>Browser: Redirection vers l'espace membre
4.2. Appel API avec Auto-Renouvellement
Le AuthorizedClientService exécute des requêtes sans que le développeur ne se soucie de l'expiration du jeton.
sequenceDiagram
participant Business as "Logique Métier"
participant Client as AuthorizedClientService
participant Renewer as TokenAutoRenewer
participant Repo as TokenRepository
participant Service as OidcService
participant IdP as "Fournisseur d'Identité"
Business->>Client: execute(userId, request)
Client->>Renewer: getValidToken(userId)
Renewer->>Repo: findByKey(userId)
Repo-->>Renewer: ExpiringTokenWrapper
alt Jeton Expiré (ou proche de l'expiration)
Note over Renewer: Verrouillage Thread-Safe (Lock par userId)
Renewer->>Service: refreshAccessToken(refreshToken)
Service->>IdP: POST /token (grant_type=refresh_token)
IdP-->>Service: New OAuth2AccessToken
Renewer->>Repo: save(userId, newWrapper)
Note over Renewer: Déverrouillage
end
Renewer-->>Client: OAuth2AccessToken valide
Client->>Service: signRequest(token, request)
Client->>Service: execute(request)
Service-->>Client: Response
Client-->>Business: Response
📊 Comparaison : Standard vs Orchestré
| Caractéristique | Flux Standard (core) |
Flux Orchestré (helpers) |
|---|---|---|
| Gestion du State | Manuelle (Session) | Automatique via AuthFlowCoordinator |
| Validation PKCE | Manuelle (Stockage verifier) | Automatique via AuthSessionContext |
| Refresh Token | Manuel (Vérification isExpired) |
Transparent via TokenAutoRenewer |
| Concurrence | Risque de Race Condition | Thread-safe avec verrouillage par clé |
| Stockage | Code spécifique à l'app | Abstraction via TokenRepository |
🛠️ Matrice des Méthodes Enterprise
| Opération | Classe | Méthode | Standard |
|---|---|---|---|
| Discovery | OidcDiscoveryService |
getProviderMetadata() |
RFC 8414 / OIDC Disc |
| PKCE | AuthorizationUrlBuilder |
initPKCE() |
RFC 7636 |
| Signature | OAuth20RequestSigner |
signRequest(token, req) |
RFC 6750 |
| Révocation | OAuth20Service |
revokeToken(token, hint) |
RFC 7009 |
| DPoP | DPoPProofCreator |
createDPoPProof(req, token) |
RFC 9449 |
5. Résilience Industrielle (Retry Policy)
Le moteur de ScribeJava intègre une boucle de résilience capable de gérer les erreurs transitoires (Rate Limit, Instabilité serveur).
graph TD
Start["OAuthService.execute"] --> Req["Exécution de la requête"]
Req --> Resp{"Code de réponse ?"}
Resp -- "2xx / 4xx (hors 429)" --> Success["Retourne la réponse"]
Resp -- "429 / 5xx" --> Retry{"RetryPolicy active ?"}
Retry -- "Non" --> Success
Retry -- "Oui" --> Check{"Max attempts atteint ?"}
Check -- "Oui" --> Success
Check -- "Non" --> Wait["Attente delayMs (Fixe ou Exponentiel)"]
Wait --> Req
Note : Le délai d'attente est fixe pour la politique globale du Core (OAuthService), mais utilise un multiplicateur exponentiel pour les requêtes du service de découverte OIDC (OidcDiscoveryService).
6. Observabilité et Diagnostic (Redaction)
La sécurité est maintenue même dans les logs grâce au mécanisme de masquage automatique des secrets.
sequenceDiagram
participant App as "Logique Métier"
participant Req as OAuthRequest
participant Log as "OAuthLogger / cURL"
App->>Req: signRequest(token)
App->>Req: toCurlCommand() / toDebugString()
Note over Req: Analyse des headers & params
Req->>Req: Identifie 'Authorization', 'secret', 'token'
Req->>Req: Remplace les valeurs par [REDACTED]
Req-->>Log: Chaîne sécurisée pour les logs
7. Cryptographie Native OIDC (Validation JWT)
ScribeJava n'utilise pas de bibliothèque externe (Zéro-Dépendance) pour valider les signatures.
flowchart TD
ID["ID Token String"] --> Parse["Jwt.parse: Base64 Decoding"]
Parse --> Header["Header: kid, alg"]
Parse --> Payload["Claims: iss, sub, aud, exp"]
Header --> Lookup{"Recherche kid dans JWKS"}
Lookup -- "Trouvé" --> Crypto["Signature.verify (java.security)"]
Lookup -- "Absent" --> Refresh["Rotation: Rechargement JWKS"]
Refresh --> Crypto
Crypto --> Claims["Validation temporelle (iat < now < exp)"]
Claims --> Final["IdToken Validé"]
8. Authentification Client (Pattern Strategy)
Flexible pour s'adapter à toutes les exigences des fournisseurs.
graph LR
Service["OAuthService"] --> Strategy{"ClientAuthentication"}
Strategy -- "Default" --> Basic["HttpBasic: Header Authorization: Basic base64"]
Strategy -- "Azure/Custom" --> Body["RequestBody: params client_id & client_secret"]
9. Pipeline d'Intercepteurs (Extensibilité)
Modification modulaire des requêtes avant l'envoi.
graph LR
Start["Requête Brute"] --> I1["Interceptor 1: Headers Custom"]
I1 --> I2["Interceptor 2: Signature de Requête / Auth Client"]
I2 --> I3["Interceptor 3: DPoP Proof"]
I3 --> Exec["Exécution Client HTTP"]
Note : Le PKCE est injecté directement au niveau de la requête de jeton d'autorisation via AuthorizationCodeGrant (et non via un intercepteur de requête réseau).
10. Liaison Cryptographique DPoP (RFC 9449)
Lien indissociable entre le jeton et la clé privée du client.
sequenceDiagram
participant App as Application
participant DPoP as DPoPProofCreator
participant IdP as "Fournisseur d'Identité"
Note over App, DPoP: Génération de Preuve (chaque appel)
App->>DPoP: createDPoPProof(request, accessToken)
DPoP->>DPoP: Hash(accessToken) → 'ath' claim
DPoP->>DPoP: Sign JWT(htm, htu, ath, jwk_public)
DPoP-->>App: En-tête 'DPoP: signed_jwt'
App->>IdP: Request + Jeton Bearer + En-tête DPoP
IdP->>IdP: Vérifie la signature du JWT avec la clé publique fournie
IdP->>IdP: Vérifie que 'ath' correspond au jeton envoyé
11. Pushed Authorization Requests (PAR - RFC 9126)
Le flux le plus sécurisé pour l'initiation : les paramètres ne passent plus par l'URL du navigateur.
sequenceDiagram
participant App as Application
participant Builder as AuthorizationUrlBuilder
participant Handler as OAuth20PushedAuthHandler
participant IdP as "Fournisseur d'Identité"
App->>Builder: build() avec usePAR=true
Builder->>Handler: pushAuthorizationRequestAsync(params)
Handler->>IdP: POST /par (params + Client Auth)
IdP-->>Handler: 201 Created (request_uri, expires_in)
Handler-->>Builder: PushedAuthorizationResponse
Builder->>Builder: Génère URL courte : ?request_uri=...
Builder-->>App: URL d'autorisation sécurisée
12. Polling du Device Flow (RFC 8628)
Gestion intelligente de l'attente active pour les terminaux IoT/Console.
graph TD
Start["Début du Polling"] --> Req["POST /token grant_type=device_code"]
Req --> Resp{"Réponse IdP ?"}
Resp -- "200 OK" --> Success["Retourne AccessToken"]
Resp -- "400 authorization_pending" --> Wait["Attente intervalSeconds"]
Resp -- "400 slow_down" --> SD["Augmentation interval +5s"]
Resp -- "400 access_denied / Autre" --> Fail["Lève OAuthResponseException"]
Wait --> Req
SD --> Wait
13. Découverte du Client HTTP (ServiceLoader)
Comment ScribeJava maintient son autonomie "Zero-Dependency" au runtime.
graph LR
Service["OAuthService Constructor"] --> SL["java.util.ServiceLoader"]
SL --> Look["Cherche HttpClientProvider.class"]
Look -- "Trouvé (OkHttp/Armeria)" --> Load["Instancie l'implémentation externe"]
Look -- "Non trouvé" --> Default["Instancie JDKHttpClient natif"]
Load --> Ready["Service prêt avec HttpClient optimisé"]
Default --> Ready
14. JWT-Secured Authorization Request (JAR - RFC 9101)
Encapsulation cryptographique de la demande d'autorisation.
sequenceDiagram
participant Builder as AuthorizationUrlBuilder
participant Conv as JarAuthorizationRequestConverter
participant Sign as RequestObjectService
Builder->>Conv: convert(params_plats)
Conv->>Sign: createRequestObject(params)
Sign->>Sign: Signe JWT (Params → Claims)
Sign-->>Conv: signed_jwt
Conv-->>Builder: Map(request = signed_jwt, client_id = ...)
Builder->>Builder: Génère URL : ?request=eyJhbG...
15. Enregistrement Dynamique (RFC 7591)
Auto-provisioning pour les environnements de confiance ou de test.
sequenceDiagram
participant App as "Logique Métier"
participant Reg as OidcRegistrationService
participant IdP as "Fournisseur d'Identité"
App->>Reg: registerClientAsync(endpoint, redirectUris, clientName, authMethod)
Reg->>Reg: JsonBuilder.build()
Reg->>IdP: POST /registration (Content-Type: application/json)
IdP->>IdP: Valide et crée le client
IdP-->>Reg: 201 Created (client_id, client_secret, ...)
Reg-->>App: Map des identifiants générés
16. Orchestration Multi-Tenant (OAuthServiceRegistry)
Centralisation de la gestion de multiples fournisseurs et clients au sein d'une même instance applicative.
graph LR
User["UserId"] --> Reg["OAuthServiceRegistry"]
Reg --> Map{"Lookup by providerId"}
Map -- "google" --> ACS1["AuthorizedClientService G"]
Map -- "github" --> ACS2["AuthorizedClientService GH"]
Map -- "ms-entra" --> ACS3["AuthorizedClientService MS"]
ACS1 --> S1["OAuth20Service"]
ACS2 --> S2["OAuth20Service"]
ACS3 --> S3["OAuth20Service"]
17. Moteur JSON Natif (Zéro-Dépendance)
Analyse récursive par descente récursive (caractère par caractère) pour garantir l'autonomie totale du runtime.
graph TD
JSON["JSON String"] --> Parser["JsonUtils.Parser (Scanner)"]
Parser --> Char{"Lecture Caractère"}
Char -- "{" --> Obj["Analyse d'Objet (Map)"]
Char -- "[" --> Arr["Analyse de Tableau (List)"]
Char -- "Clé / Valeur" --> Val{"Type de Valeur ?"}
Val -- "String/Number/Bool/Null" --> Add["Ajout à la Map courante"]
Val -- "{" --> RecObj["Appel Récursif (Analyse d'Objet)"]
Val -- "[" --> RecArr["Appel Récursif (Analyse de Tableau)"]
RecObj --> Parser
RecArr --> Parser
Add --> Parser
Parser -- "Fin de chaîne" --> Result["Map/List finale d'objets Java"]
18. Système d'Audit et d'Événements (Hooks)
Points d'ancrage pour le monitoring et la traçabilité métier.
sequenceDiagram
participant App as "Logique Métier"
participant Service as OAuth20Service
participant Hook as OAuthEventListener
participant Net as "Client HTTP"
App->>Service: execute(request)
Service->>Hook: onTokenRequested(request)
Service->>Net: Envoi réseau
Net-->>Service: Réponse
Service->>Hook: onTokenReceived(token)
Service-->>App: Response
Note over Service, Hook: En cas d'échec
Service->>Hook: onError(exception)
19. Pont des Adaptateurs HTTP (Bridge Pattern)
Découplage entre les modèles ScribeJava et les bibliothèques tierces.
graph LR
SR["Scribe Request"] --> Bridge["HttpClient Implementation"]
Bridge --> Native["Native Model"]
subgraph OkHttp
Native1["okhttp3.Request"]
end
subgraph Armeria
Native2["com.linecorp.armeria.HttpRequest"]
end
Bridge -- "okhttp-adaptor" --> Native1
Bridge -- "armeria-adaptor" --> Native2
Native1 -- "Execute" --> Resp1["okhttp3.Response"]
Resp1 --> SResp["Scribe Response"]
20. Stratégie de Cache de Découverte OIDC
Optimisation des performances et réduction de la charge réseau IdP.
graph TD
Start["OidcDiscoveryCache.getMetadata"] --> Check{"Présent en cache ?"}
Check -- "Oui" --> Return["Retourne les métadonnées"]
Check -- "Non" --> Fetch["Appel Réseau /.well-known"]
Fetch --> Store["Mise en cache thread-safe (ConcurrentHashMap)"]
Store --> Return