Salesforce : Apex Http callout

Avec Apex nous avons la possibilités d'appeler des services extérieurs à l'org Salesforce. En utilisant les NamedCredentials nous avons les méthodes suivantes qui sont disponibles:
Les protocols d'authentification des systèmes extérieurs à Salesforce
No Authentication
Comme son nom l'indication, il n'y pas d'authentification. Le Named Credential nous met à disposition l'url qu'on pourrait utiliser lors de l'appel http. Et de façon générale, l'utilisation des Named Credentials nous permet de modifier l'url des services externieurs sans modifier le code. Très utile lors qu'on a plusieurs environnements.
Password
Un nom d'utilisateur et un mot de passe statiques sont utilisés pour s'authentifier directement dans le système externe.
Si vous utilisez le type d'identité par utilisateur, chaque utilisateur accédant au système externe gère son propre nom d'utilisateur et mot de passe.
OAuth 2.0
Un utilisateur ou l'administrateur applique des informations d'identification pour un système OAuth 2.0 spécifié qui s'authentifie dans le système externe.
L'utilisation d'un Named Credential de type OAuth 2.0
nécessite la mise en place d'un fournisseur d'authentification c'est à dire créer une
implementation de la class Auth.AuthProviderPluginClass
.
En général, c'est le grant_type
client_credentials qui est utilisé, sans intervention de l'utilisateur. Nous y reviendrons
dans la suite ce poste.
JWT
Un JWT, ou un jeton Web JSON, gère l'authentification de votre organisation dans le système externe.
Les utilisateurs n'ont pas besoin de gérer leurs propres informations d'identification pour le système externe. Lorsque les utilisateurs affichent leurs paramètres d'authentification pour les systèmes externes, ils ne peuvent pas voir les options utilisant ce protocole d'authentification. Le sujet est une chaîne lorsque le type d'identité est nommé principal, et c'est une formule lorsque le type d'identité est par utilisateur. Les certificats de signature ne sont pas inclus dans les packages. Si vous utilisez JWT ou JWT Token Exchange comme protocole d'authentification pour un identifiant nommé packagé, recréez le certificat de signature référencé du pack dans l'organisation abonnée avant d'installer le pack.
JWT Token Exchange
Un jeton JWT est envoyé à un fournisseur d'autorisation, similaire à OAuth 2.0, et reçoit en retour un jeton utilisé pour s'authentifier dans le système externe.
Dans ce protocol, il est nécessaire d'importer les clés de signature du token dans l'org Salesforce.
AWS Signature Version 4
Un protocole pour authentifier les appels aux ressources dans Amazon Web Services via HTTP.
- Resource : Choose an Authentication Protocol
Utilisation des Named Credentials dans les appels HTTP.
Imaginons que nous disposons d'un système extérieur
nommé service-pwd
qui utilise la protocole d'authentification
Password
.
Pour faire appel à un des services de service-pwd
disons la liste des
utilisateurs.
Nous pouvons procéder comme décrit dans le code ci-dessous en utilisant un Named Credential
service_pwd
.
HttpRequest query = new HttpRequest();
query.setMethod('GET');
query.setEndPoint('callout:service_pwd/users'); // Le nom d'utilisateur et le mot de passe sont définis dans le Named Credential service_pwd.
Http http = new http();
HttpResponse response = http.send(query);
String responseBody = response.getBody();
if(response.getStatusCode() == 200) {
// Do something with responseBody.
} else {
// Handle error.
}
Utilisation mixte des protocols d'authentification.
Il arrive plus souvent que les services extérieurs utilisent des implementations différentes des protocols d'authentification sités plus haut.
Par exemple, lors que le service utilise le
grant_type client_credentials
, nous devons envoyer les informations du client
et en retour le service d'authentification nous retourne le token d'accès pour accéder à la
ressource demandée.
Pour cela nous pouvons utiliser deux Named Credentials:
- Un Named Credential pour l'authentification :
client_auth
Dans ce NC nous utilisons le protocolPassword
- Un Named Credential pour accéder à la resource avec le token
obtenu avec le service d'authentification :
client_resource
Dans ce NC nous utilisons No Password
sachant que l'accès token sera fourni par le service d'authentification.
HttpRequest query = new HttpRequest();
query.setMethod('POST');
query.setEndPoint('callout:client_auth'); // Le nom d'utilisateur et le mot de passe sont définis dans le Named Credential service_pwd.
query.setHeader('Content-Type', 'application/x-www-form-urlencoded');
query.setBody('grant_type=client_credentials&scope=scopes&audience=audiences' +'&'+ 'client_id='+'{!$Credential.Username}' +'&'+ 'client_secret=' + '{!$Credential.Password}');
Http http = new http();
HttpResponse response = http.send(query);
String responseBody = response.getBody();
if(response.getStatusCode() == 200) {
Map<String, Object> resultsBody = (Map<String, Object>) JSON.deserializeUntyped(responseBody);
String accessToken = resultsBody.get('access_token');
HttpRequest resourceQuery = new HttpRequest();
resourceQuery.setMethod('GET');
resourceQuery.setEndPoint('callout:client_resource/resources');
resourceQuery.setHeader('Authorization', 'Bearer ' + accessToken);
Http resourceHttp = new http();
HttpResponse resourceResponse = resourceHttp.send(resourceQuery);
if(resourceResponse.getStatusCode() == 200) {
// Handle Resource response
} else {
// Handle Error.
}
} else {
// Handle error.
}
Utilisation des callouts personnalisés.
Maintenant imaginons que nous ayons une dizaine de services extérieurs à appeler, le code peut rapiment être redondant.
Imaginons que quelque soit le service nous puissions utiliser le même code que dans la section Utilisation des Named Credentials dans les appels HTTP ?
En fait, c'est ce que nous allons faire par la suite.
Nous allons mettre en place une classe qui va masquer la phase d'authentification comme l'utilisation normal des NC le fait.
public class HttpExt {
public static final long TOKEN_VALID_FOR = 3600; // one hour.
private static final String TEST_ENDPOINT = 'https://token-endpoint.com';
private String method;
private String endPoint;
private final HttpRequest request;
public HttpExt() {
this.request = new HttpRequest();
}
public static JWTExchange__mdt getJWTConfig(String name) {
// Retrieve JWTExchange__mdt by DeveloperName =: name
}
public static OAuth2Parameter__mdt getClientCredentialsConfig(String name) {
// Retrieve OAuth2Parameter__mdt by DeveloperName =: name
}
public static String getJWTToken(JWTExchange__mdt config) {
return ''; // JWT Token
}
public static String getAccessToken(String tokenUrl, String requestBody) {
// Send HTTP request
}
public static String exchangeToken(JWTExchange__mdt config) {
String JWTtoken = getJWTToken(config);
String tokenUrl = config.TokenUrl__c;
String body = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=' + JWTtoken;
return getAccessToken(tokenUrl, body);
}
/**
* client_id and client_secret must be defined in NamedCredetials
*/
public static String clientCredentialsToken(OAuth2Parameter__mdt config) {
String tokenUrl = config.TokenUrl__c;
String body = 'grant_type=client_credentials' +'&'+ 'client_id='+'{!$Credential.Username}' +'&'+ 'client_secret=' + '{!$Credential.Password}';
if (String.isNotBlank(config.Scope__c)) {
body += '&scope=' + config.Scope__c;
}
if (String.isNotBlank(config.Audience__c)) {
body += '&audience=' + config.Audience__c;
}
return getAccessToken(tokenUrl, body);
}
public HttpResponse send() {
String finalEndPoint = this.endPoint;
if (finalEndPoint.startsWith('jwt:')) { // Start JWT Exchange
finalEndPoint = finalEndPoint.substring(4);
String name = finalEndPoint.split('/')[0];
JWTExchange__mdt config = getJWTConfig(name);
String accessToken = exchangeToken(config);
request.setHeader('Authorization', 'Bearer ' + accessToken);
finalEndPoint = 'callout:' + finalEndPoint;
} else if (finalEndPoint.startsWith('client_credentials:')) {
finalEndPoint = finalEndPoint.substring(19);
String name = finalEndPoint.split('/')[0];
OAuth2Parameter__mdt config = getClientCredentialsConfig(name);
String accessToken = clientCredentialsToken(config);
request.setHeader('Authorization', 'Bearer ' + accessToken);
finalEndPoint = 'callout:' + finalEndPoint;
}
// Add more protocol
request.setEndpoint(finalEndPoint);
request.setMethod(method);
Http http = new Http();
return http.send(request);
}
public class HttpExtException extends Exception {}
}
Nous pouvons maintenant utiliser la
HttpExt
pour effectuer nos appels vers les différents services.
HttpExt query = new HttpExt();
query.setMethod('GET');
query.setEndPoint('client_credentials:client_resource/resources'); // Les Custom Metadata OAuth2Parameter__mdt doit avoir un enregistrement dont le DeveloperName est client_resource le même nom que le NC.
// Le CM client_resource.TokenUrl__c doit être callout:client_auth
HttpResponse response = query.send();
String responseBody = response.getBody();
if(response.getStatusCode() == 200) {
// Do something with responseBody.
} else {
// Handle error.
}
Une fois tous les services configurés dans le code Apex nous n'aurons qu'à écrire les 6 lignes pour accéder à nos resources.
Si nous avons un autre service utilisant le JWT Token Exchange le code sera :
HttpExt query = new HttpExt();
query.setMethod('GET');
query.setEndPoint('jwt:jwt_resource/resources'); // Les Custom Metadata JWTExchange__mdt doit avoir un enregistrement dont le DeveloperName est jwt_resource le même nom que le NC.
// Le CM jwt_resource.TokenUrl__c doit être callout:jwt_auth
HttpResponse response = query.send();
String responseBody = response.getBody();
if(response.getStatusCode() == 200) {
// Do something with responseBody.
} else {
// Handle error.
}