OAuth 2.0 et JWT (JSON Web Tokens) sont les mecanismes d’authentification standard pour les applications cloud modernes. Dans ABAP Cloud, ils permettent une communication securisee de service a service et une authentification utilisateur sans mots de passe dans le code.
Concepts fondamentaux
Apercu OAuth 2.0
OAuth 2.0 est un framework d’autorisation qui permet aux applications un acces controle aux ressources :
| Terme | Description |
|---|---|
| Resource Owner | L’utilisateur ou systeme proprietaire de la ressource |
| Client | L’application qui demande l’acces (par ex. ABAP Cloud) |
| Authorization Server | Emet les Access Tokens (par ex. XSUAA) |
| Resource Server | Heberge l’API protegee |
| Access Token | Token de courte duree pour l’acces API |
| Refresh Token | Token de longue duree pour le renouvellement des tokens |
JWT (JSON Web Token)
JWT est le format de token utilise par OAuth 2.0 :
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiYXVkIjoibXktYXBwIiwiaXNzIjoieHN1YWEifQ.signature_hereUn JWT se compose de trois parties (encodees en Base64, separees par des points) :
| Partie | Contenu |
|---|---|
| Header | Algorithme et type de token |
| Payload | Claims (ID utilisateur, scopes, expiration) |
| Signature | Signature cryptographique pour verification |
Claims JWT importants
| Claim | Description |
|---|---|
sub | Subject - ID utilisateur ou client |
aud | Audience - Public cible du token |
iss | Issuer - Emetteur (par ex. URL XSUAA) |
exp | Expiration - Timestamp Unix d’expiration |
iat | Issued At - Timestamp d’emission |
scope | Scopes d’autorisation |
zid | Zone ID - Identifiant du tenant |
Flows OAuth 2.0
Client Credentials Flow
Le Client Credentials Flow est utilise pour la communication service-a-service quand aucun utilisateur n’est implique :
+-------------------------------------------------------------------------+| Client Credentials Flow || || +-------------+ +---------------------+ || | ABAP Cloud | | XSUAA | || | (Client) | | (Auth Server) | || +------+------+ +----------+----------+ || | | || | 1. POST /oauth/token | || | grant_type=client_credentials | || | client_id=xxx | || | client_secret=yyy | || |------------------------------------------------>| || | | || | 2. Access Token (JWT) | || |<------------------------------------------------| || | | || +------+------+ +----------+----------+ || | ABAP Cloud | | Resource Server | || | (Client) | | (API) | || +------+------+ +----------+----------+ || | | || | 3. Appel API avec Bearer Token | || | Authorization: Bearer <token> | || |------------------------------------------------>| || | | || | 4. Reponse API | || |<------------------------------------------------| |+-------------------------------------------------------------------------+Cas d’utilisation :
- Communication backend-a-backend
- Jobs batch sans contexte utilisateur
- Scenarios d’integration technique
Authorization Code Flow
Le Authorization Code Flow est utilise quand un utilisateur est implique :
+-------------------------------------------------------------------------+| Authorization Code Flow || || +---------+ +-------------+ +-------------+ +--------------+ || | Browser | | ABAP Cloud | | XSUAA | | Resource API | || +----+----+ +------+------+ +------+------+ +-------+------+ || | | | | || | 1. Login | | | || |--------------->| | | || | | | | || | 2. Redirect | | | || | vers XSUAA | | | || |<---------------| | | || | | | || | 3. Page de login | | || |---------------------------------->| | || | | | || | 4. Redirect avec Authorization Code | || |<----------------------------------| | || | | | | || | 5. Code | | | || |--------------->| | | || | | 6. Token Request | | || | |----------------->| | || | | 7. Access Token | | || | |<-----------------| | || | | | | || | | 8. Appel API | | || | |-------------------------------------->| || | | 9. Reponse | | || | |<--------------------------------------| |+-------------------------------------------------------------------------+Cas d’utilisation :
- Applications web avec login utilisateur
- Single Sign-On (SSO)
- Applications Fiori
XSUAA dans SAP BTP
XSUAA (Extended Services for User Account and Authentication) est le serveur OAuth 2.0 central sur SAP BTP :
Service Binding XSUAA dans ABAP Cloud
Les credentials XSUAA sont fournis via un Service Binding :
CLASS zcl_xsuaa_config DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_xsuaa_credentials, clientid TYPE string, clientsecret TYPE string, url TYPE string, token_url TYPE string, END OF ty_xsuaa_credentials.
CLASS-METHODS: get_credentials RETURNING VALUE(rs_credentials) TYPE ty_xsuaa_credentials RAISING cx_abap_context_info_error.
ENDCLASS.
CLASS zcl_xsuaa_config IMPLEMENTATION.
METHOD get_credentials. " Lire les credentials XSUAA depuis le Service Binding " Ceux-ci sont automatiquement injectes par le systeme
DATA(lo_service_manager) = cl_ams_service_manager=>get_instance( ).
DATA(ls_binding) = lo_service_manager->get_service_binding( iv_service_type = 'xsuaa" ).
" Extraire les credentials rs_credentials-clientid = ls_binding-credentials-clientid. rs_credentials-clientsecret = ls_binding-credentials-clientsecret. rs_credentials-url = ls_binding-credentials-url. rs_credentials-token_url = |{ ls_binding-credentials-url }/oauth/token|. ENDMETHOD.
ENDCLASS.Recuperer un token avec Client Credentials
Requete de token OAuth 2.0
CLASS zcl_oauth_client DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_token_response, access_token TYPE string, token_type TYPE string, expires_in TYPE i, scope TYPE string, END OF ty_token_response.
METHODS: constructor IMPORTING is_credentials TYPE zcl_xsuaa_config=>ty_xsuaa_credentials,
get_access_token RETURNING VALUE(rv_token) TYPE string RAISING cx_web_http_client_error.
PRIVATE SECTION. DATA ms_credentials TYPE zcl_xsuaa_config=>ty_xsuaa_credentials. DATA ms_token_cache TYPE ty_token_response. DATA mv_token_expiry TYPE timestamp.
METHODS: request_new_token RETURNING VALUE(rs_token) TYPE ty_token_response RAISING cx_web_http_client_error,
is_token_valid RETURNING VALUE(rv_valid) TYPE abap_bool.
ENDCLASS.
CLASS zcl_oauth_client IMPLEMENTATION.
METHOD constructor. ms_credentials = is_credentials. ENDMETHOD.
METHOD get_access_token. " Utiliser le token du cache s'il est encore valide IF is_token_valid( ) = abap_true. rv_token = ms_token_cache-access_token. RETURN. ENDIF.
" Demander un nouveau token ms_token_cache = request_new_token( ).
" Calculer l'expiration (avec 60 secondes de marge) GET TIME STAMP FIELD DATA(lv_now). mv_token_expiry = cl_abap_tstmp=>add( tstmp = lv_now secs = ms_token_cache-expires_in - 60 ).
rv_token = ms_token_cache-access_token. ENDMETHOD.
METHOD request_new_token. " Creer le client HTTP pour l'endpoint token DATA(lo_destination) = cl_http_destination_provider=>create_by_url( i_url = ms_credentials-token_url ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
DATA(lo_request) = lo_client->get_http_request( ).
" Authentification Basic pour Client Credentials DATA(lv_auth_string) = |{ ms_credentials-clientid }:{ ms_credentials-clientsecret }|. DATA(lv_auth_base64) = cl_web_http_utility=>encode_base64( lv_auth_string ).
lo_request->set_header_field( i_name = 'Authorization" i_value = |Basic { lv_auth_base64 }| ).
lo_request->set_header_field( i_name = 'Content-Type" i_value = 'application/x-www-form-urlencoded" ).
" Corps de la requete token lo_request->set_text( 'grant_type=client_credentials' ).
" Executer la requete DATA(lo_response) = lo_client->execute( if_web_http_client=>post ). DATA(lv_status) = lo_response->get_status( )-code.
IF lv_status <> 200. lo_client->close( ). RAISE EXCEPTION TYPE cx_web_http_client_error. ENDIF.
" Parser la reponse DATA(lv_json) = lo_response->get_text( ). lo_client->close( ).
/ui2/cl_json=>deserialize( EXPORTING json = lv_json CHANGING data = rs_token ). ENDMETHOD.
METHOD is_token_valid. IF ms_token_cache-access_token IS INITIAL. rv_valid = abap_false. RETURN. ENDIF.
GET TIME STAMP FIELD DATA(lv_now). rv_valid = xsdbool( lv_now < mv_token_expiry ). ENDMETHOD.
ENDCLASS.Token avec Communication Arrangement
Dans les scenarios productifs, les Communication Arrangements devraient etre utilises :
CLASS zcl_oauth_comm_arrangement DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: call_protected_api IMPORTING iv_path TYPE string RETURNING VALUE(rv_response) TYPE string RAISING cx_web_http_client_error cx_http_dest_provider_error.
ENDCLASS.
CLASS zcl_oauth_comm_arrangement IMPLEMENTATION.
METHOD call_protected_api. " Destination avec OAuth 2.0 Client Credentials depuis Communication Arrangement " La destination gere automatiquement les tokens
DATA(lo_destination) = cl_http_destination_provider=>create_by_comm_arrangement( comm_scenario = 'Z_EXTERNAL_API" service_id = 'Z_EXT_API_REST" comm_system_id = 'EXT_SYSTEM" ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( iv_path ).
" Le token est automatiquement ajoute par la destination DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
rv_response = lo_response->get_text( ). lo_client->close( ). ENDMETHOD.
ENDCLASS.Validation des tokens JWT
Parser la structure du token
CLASS zcl_jwt_parser DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_jwt_header, alg TYPE string, typ TYPE string, kid TYPE string, END OF ty_jwt_header,
BEGIN OF ty_jwt_payload, sub TYPE string, aud TYPE string, iss TYPE string, exp TYPE i, iat TYPE i, scope TYPE string, zid TYPE string, client_id TYPE string, END OF ty_jwt_payload,
BEGIN OF ty_jwt_parts, header TYPE ty_jwt_header, payload TYPE ty_jwt_payload, signature TYPE string, END OF ty_jwt_parts.
METHODS: parse IMPORTING iv_token TYPE string RETURNING VALUE(rs_parts) TYPE ty_jwt_parts RAISING cx_abap_invalid_value.
PRIVATE SECTION. METHODS: decode_base64url IMPORTING iv_encoded TYPE string RETURNING VALUE(rv_decoded) TYPE string.
ENDCLASS.
CLASS zcl_jwt_parser IMPLEMENTATION.
METHOD parse. " Diviser le token en trois parties SPLIT iv_token AT '.' INTO DATA(lv_header_b64) DATA(lv_payload_b64) DATA(lv_signature).
IF lv_header_b64 IS INITIAL OR lv_payload_b64 IS INITIAL. RAISE EXCEPTION TYPE cx_abap_invalid_value. ENDIF.
" Decoder et parser le header DATA(lv_header_json) = decode_base64url( lv_header_b64 ). /ui2/cl_json=>deserialize( EXPORTING json = lv_header_json CHANGING data = rs_parts-header ).
" Decoder et parser le payload DATA(lv_payload_json) = decode_base64url( lv_payload_b64 ). /ui2/cl_json=>deserialize( EXPORTING json = lv_payload_json CHANGING data = rs_parts-payload ).
rs_parts-signature = lv_signature. ENDMETHOD.
METHOD decode_base64url. " Convertir Base64URL en Base64 standard DATA(lv_base64) = iv_encoded. REPLACE ALL OCCURRENCES OF '-' IN lv_base64 WITH '+'. REPLACE ALL OCCURRENCES OF '_' IN lv_base64 WITH '/'.
" Ajouter le padding si necessaire DATA(lv_padding) = strlen( lv_base64 ) MOD 4. IF lv_padding > 0. lv_base64 = lv_base64 && repeat( val = '=' occ = 4 - lv_padding ). ENDIF.
" Decoder Base64 rv_decoded = cl_web_http_utility=>decode_base64( lv_base64 ). ENDMETHOD.
ENDCLASS.Implementer la validation de token
CLASS zcl_jwt_validator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. TYPES: BEGIN OF ty_validation_result, valid TYPE abap_bool, error_msg TYPE string, jwt_payload TYPE zcl_jwt_parser=>ty_jwt_payload, END OF ty_validation_result.
METHODS: constructor IMPORTING iv_expected_issuer TYPE string iv_expected_audience TYPE string,
validate IMPORTING iv_token TYPE string RETURNING VALUE(rs_result) TYPE ty_validation_result.
PRIVATE SECTION. DATA mv_expected_issuer TYPE string. DATA mv_expected_audience TYPE string.
METHODS: validate_expiration IMPORTING iv_exp TYPE i RETURNING VALUE(rv_valid) TYPE abap_bool,
validate_issuer IMPORTING iv_issuer TYPE string RETURNING VALUE(rv_valid) TYPE abap_bool,
validate_audience IMPORTING iv_audience TYPE string RETURNING VALUE(rv_valid) TYPE abap_bool.
ENDCLASS.
CLASS zcl_jwt_validator IMPLEMENTATION.
METHOD constructor. mv_expected_issuer = iv_expected_issuer. mv_expected_audience = iv_expected_audience. ENDMETHOD.
METHOD validate. DATA(lo_parser) = NEW zcl_jwt_parser( ).
TRY. DATA(ls_jwt) = lo_parser->parse( iv_token ). CATCH cx_abap_invalid_value. rs_result-valid = abap_false. rs_result-error_msg = 'Format de token invalide'. RETURN. ENDTRY.
" 1. Verifier l'expiration IF validate_expiration( ls_jwt-payload-exp ) = abap_false. rs_result-valid = abap_false. rs_result-error_msg = 'Token expire'. RETURN. ENDIF.
" 2. Verifier l'emetteur IF validate_issuer( ls_jwt-payload-iss ) = abap_false. rs_result-valid = abap_false. rs_result-error_msg = |Emetteur invalide : { ls_jwt-payload-iss }|. RETURN. ENDIF.
" 3. Verifier l'audience IF validate_audience( ls_jwt-payload-aud ) = abap_false. rs_result-valid = abap_false. rs_result-error_msg = |Audience invalide : { ls_jwt-payload-aud }|. RETURN. ENDIF.
" Token valide rs_result-valid = abap_true. rs_result-jwt_payload = ls_jwt-payload. ENDMETHOD.
METHOD validate_expiration. " Convertir Unix Timestamp en ABAP Timestamp DATA(lv_exp_ts) = cl_abap_tstmp=>utclong_unix_from_i( iv_exp ).
GET TIME STAMP FIELD DATA(lv_now). rv_valid = xsdbool( lv_now < lv_exp_ts ). ENDMETHOD.
METHOD validate_issuer. rv_valid = xsdbool( iv_issuer CS mv_expected_issuer ). ENDMETHOD.
METHOD validate_audience. rv_valid = xsdbool( iv_audience = mv_expected_audience ). ENDMETHOD.
ENDCLASS.Authentifier les requetes entrantes
Quand votre application ABAP Cloud agit comme Resource Server :
Extraire le JWT de la requete HTTP
CLASS zcl_request_authenticator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: authenticate_request IMPORTING io_request TYPE REF TO if_web_http_request RETURNING VALUE(rs_result) TYPE zcl_jwt_validator=>ty_validation_result.
PRIVATE SECTION. METHODS: extract_bearer_token IMPORTING io_request TYPE REF TO if_web_http_request RETURNING VALUE(rv_token) TYPE string.
ENDCLASS.
CLASS zcl_request_authenticator IMPLEMENTATION.
METHOD authenticate_request. " Extraire le Bearer Token du header Authorization DATA(lv_token) = extract_bearer_token( io_request ).
IF lv_token IS INITIAL. rs_result-valid = abap_false. rs_result-error_msg = 'Aucun Bearer token fourni'. RETURN. ENDIF.
" Valider le token DATA(lo_validator) = NEW zcl_jwt_validator( iv_expected_issuer = 'https://mysubaccount.authentication.eu10.hana.ondemand.com" iv_expected_audience = 'my-application-clientid" ).
rs_result = lo_validator->validate( lv_token ). ENDMETHOD.
METHOD extract_bearer_token. DATA(lv_auth_header) = io_request->get_header_field( 'Authorization' ).
IF lv_auth_header CP 'Bearer *'. rv_token = lv_auth_header+7. " 'Bearer ' = 7 caracteres ENDIF. ENDMETHOD.
ENDCLASS.Autorisation basee sur les scopes
CLASS zcl_scope_authorizer DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS: has_scope IMPORTING iv_scope_string TYPE string iv_required_scope TYPE string RETURNING VALUE(rv_has_scope) TYPE abap_bool,
check_scope IMPORTING iv_scope_string TYPE string iv_required_scope TYPE string RAISING zcx_not_authorized.
ENDCLASS.
CLASS zcl_scope_authorizer IMPLEMENTATION.
METHOD has_scope. " Les scopes sont separes par des espaces SPLIT iv_scope_string AT ' ' INTO TABLE DATA(lt_scopes).
READ TABLE lt_scopes TRANSPORTING NO FIELDS WITH KEY table_line = iv_required_scope.
rv_has_scope = xsdbool( sy-subrc = 0 ). ENDMETHOD.
METHOD check_scope. IF has_scope( iv_scope_string = iv_scope_string iv_required_scope = iv_required_scope ) = abap_false. RAISE EXCEPTION TYPE zcx_not_authorized EXPORTING textid = zcx_not_authorized=>missing_scope scope = iv_required_scope. ENDIF. ENDMETHOD.
ENDCLASS.Bonnes pratiques
A faire
| Recommandation | Justification |
|---|---|
| Utiliser Communication Arrangements | Gestion automatique des tokens |
| Mettre en cache les tokens | Moins de requetes vers le serveur d’auth |
| Courte duree de vie des tokens | Minimise les degats en cas de fuite |
| Scopes minimaux | Principe du moindre privilege |
| Validation des tokens cote serveur | Jamais uniquement cote client |
A eviter
| A eviter | Risque |
|---|---|
| Secrets dans le code | Faille de securite |
| Tokens dans les logs | Exposition des credentials |
| Tokens dans les URLs | Visible dans l’historique du navigateur |
| Ne pas verifier la signature | Falsification de token possible |
| Accepter les tokens expires | Attaques par rejeu |
Sujets complementaires
- HTTP Client - Appeler des APIs REST
- Authorization Checks - Controles d’autorisation
- Guide Communication Scenarios - Configurer les Communication Arrangements
- Multitenancy dans ABAP Cloud - Tokens specifiques au tenant
Resume
OAuth 2.0 et JWT sont essentiels pour les applications cloud securisees :
- Client Credentials Flow pour la communication service-a-service sans utilisateur
- Authorization Code Flow pour l’authentification basee sur l’utilisateur
- XSUAA est le serveur OAuth 2.0 central sur SAP BTP
- Validation JWT comprend la verification de signature, expiration, emetteur et audience
- Communication Arrangements simplifient considerablement la gestion des tokens
- Scopes permettent une autorisation fine
Utilisez Communication Arrangements et Destinations pour les scenarios productifs - ils gerent automatiquement le cycle de vie complet des tokens.