Aller au contenu

🗺️ 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 du PKCE.

  • 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 :

GET /.well-known/openid-configuration HTTP/1.1
Host: accounts.google.com
* Données reçues : Un document JSON (désérialisé en 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) :

POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Paramètres du corps : * 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ètres refresh_token et client_id/secret.

  • OAuth2AccessToken : Contient le nouveau jeton et sa date d'expiration calculée via getExpiresAt().


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

⬅️ Retour au README principal