Article
· Juin 7 10m de lecture

Création d'une API REST avec authentification JWT en ObjectScript

Avant-propos

Les versions 2022.2 et ultérieures d'InterSystems IRIS offrent la possibilité de s'authentifier auprès d'une API REST à l'aide de jetons web (JWT) JSON. Cette fonctionnalité renforce la sécurité en limitant le lieu et la fréquence de transfert des mots de passe sur le réseau et en fixant un délai d'expiration pour l'accès.

L'objectif de cet article est de servir de tutoriel sur la façon d'implémenter une API REST fictive en utilisant InterSystems IRIS et de verrouiller l'accès à cette API par le biais de JWT.

REMARQUE Je ne suis PAS développeur. Je ne fais aucune déclaration quant à l'efficacité, l'évolutivité ou la qualité des échantillons de code que j'utilise dans cet article. Les exemples ci-dessous sont donnés UNIQUEMENT à des fins éducatives. Ils ne sont PAS destinés à un code de production.

Prologue

Cette réserve étant faite, explorons les concepts que nous allons décortiquer ici.

Qu'est-ce que REST?

Le terme REST est l'acronyme de REpresentational State Transfer. Il s'agit d'un style d'architecture pour la conception d'applications connectées et pour l'accès à des fonctions publiées par ces applications.

Qu'est-ce qu'un JWT?

Un jeton web JSON (JWT) est un moyen compact et sûr au niveau de l'URL qui permet de représenter les demandes transférées entre deux parties et qui peuvent être signées numériquement, cryptées ou les deux à la fois. Si vous souhaitez en savoir plus sur les JWT et les autres classes web JSON prises en charge par InterSystems IRIS, lisez cet article.

Se salir les mains

Selon les spécifications

Pour consommer une API REST, il faut d'abord disposer d'une API REST. J'ai fourni ici un exemple de spécification OpenAPI 2.0 qui est adapté au jeu de rôle sur table (TTRPG). Je l'utiliserai tout au long des exemples présentés ici. Il existe de nombreux exemples de rédaction en ligne, alors n'hésitez pas à vous y plonger, mais la spécification n'est qu'un plan directeur. Il ne fait rien d'autre que de nous informer sur l'utilisation de l'API.

Génération d'API REST

InterSystems IRIS fournit une manière très soignée de générer des souches de code pour l'API REST. Cette documentation fournit une méthode complète de génération de souches de code. N'hésitez pas à utiliser la spécification OpenAPI 2.0 que j'ai fournie dans la section précédente.

Mise en œuvre

C'est là que nous allons creuser. La section de génération aura créé trois fichiers .cls pour vous :

  1. impl.cls
  2. disp.cls
  3. spec.cls

Nous allons passer le plus de temps possible dans impl.cls, peut-être toucher à disp.cls pour le débogage, et laisser spec.cls tranquille.

Dans impl.cls se trouvent les souches de code pour les méthodes que disp.cls appellera lorsqu'il recevra une requête API. La spécification OpenAPI a défini ces signatures. Elle peut dire ce que vous voulez qu'elle fasse, mais c'est vous qui devez la mettre en œuvre. Alors faisons ça!

Création

L'une des façons dont nous utilisons une base de données est d'y ajouter des objets. Ces objets servent de base à nos autres fonctions. Sans objets existants, nous n'aurons rien à voir, nous allons donc commencer par notre modèle d'objet : un caractère Character !

Un Character aura nécessairement un nom et spécifiera éventuellement sa classe, sa race et son niveau. Voici un exemple d'implémentation de la classe TTRPG.Character.

Class TTRPG.Character Extends %Persistent
{

Property Name As %String [ Required ];

Property Race As %String;

Property Class As %String;

Property Level As %String;

Index IndexName On Name [ IdKey ];

ClassMethod GetCharByName(name As %String) As TTRPG.Character
{
set character = ##class(TTRPG.Character).%OpenId(name)

Quit character
}
}

Puisque nous voulons stocker les objets Character dans la base de données, il nous faut hériter de la classe %Persistent. Nous voulons être capables de rechercher nos charactères par leur nom plutôt que de leur assigner une clé d'identification arbitraire, donc nous définissons l'attribut [ IdKey ] sur l'index pour la propriété Character.Name. Cela garantit également l'unicité du nom du caractère.

Une fois notre modèle d'objet fondamental défini, nous pouvons analyser la mise en œuvre de l'API REST. La première méthode que nous allons explorer est la méthode PostCharacter.

En résumé, cette partie consomme une requête HTTP POST vers le point de terminaison /characters avec les propriétés de caractères que nous avons définies dans le corps de la requête. Il devrait prendre les arguments fournis et créer un objet TTRPG.Character à partir de ceux-ci, le sauvegarder dans la base de données, et nous faire savoir s'il a réussi ou non.

ClassMethod PostCharacter(name As %String, class As %String, race As %String, level As %String) As %DynamicObject
{
set results = {} // create the return %DynamicObject

//créer l'objet caractère
set char = ##class(TTRPG.Character).%New()

set char.Name = name
set char.Class = class
set char.Race = race
set char.Level = level
set st = char.%Save()

if st {
set charInfo = {}
set charInfo.Name = char.Name
set charInfo.Class = char.Class
set charInfo.Race = char.Race
set charInfo.Level = char.Level
set results.Character = charInfo
Set results.Status = "success"
}
else {
Set results.Status = "error"
Set results.Message = "Unable to create the character"
}
Quit results
}

Désormais, nous pouvons créer des caractères, mais comment récupérer celui que nous venons de créer ? Selon la spécification OpenAPI, le point de terminaison /characters/{charName} nous permet de récupérer un caractère par son nom. Nous récupérons l'instance de caractère, si elle existe. S'il n'existe pas, nous renvoyons une erreur indiquant à l'utilisateur qu'un caractère avec le nom fourni n'existe pas. Ceci est mis en œuvre dans la méthode GetCharacterByName.

ClassMethod GetCharacterByName(charName As %String) As %DynamicObject
{
// Créer un nouvel objet dynamique pour stocker les résultats
Définir les résultats = {}

set char = ##class(TTRPG.Character).GetCharByName(charName)

if char {
set charInfo = {}
set charInfo.Name = char.Name
set charInfo.Class = char.Class
set charInfo.Race = char.Race
set charInfo.Level = char.Level
set results.Character = charInfo
Set results.Status = "success"
}
// Si aucun caractère n'a été trouvé, un message d'erreur est affiché dans l'objet de résultats.
else {
Set results.Status = "error"
Set results.Message = "No characters found"
}

// Renvoyer l'objet de résultats
Quit results
}

Mais c'est juste votre caractère. Qu'en est-il de tous les autres caractères créés par d'autres personnes ? Nous pouvons visualiser ces caractères en utilisant la méthode GetCharacterList. Il consomme une requête HTTP GET vers le point de terminaison /characters pour compiler et renvoyer une liste de tous les caractères de la base de données.

ClassMethod GetCharacterList() As %DynamicObject
{
// Créer un nouvel objet dynamique pour stocker les résultats
Définir les résultats = {}
set query = "SELECT Name, Class, Race, ""Level"" FROM TTRPG.""Character"""
set tStatement = ##class(%SQL.Statement).%New()
set qstatus = tStatement.%Prepare(query)
if qstatus '= 1 { Do ##class(TTRPG.impl).%WriteResponse("Error: " _ $SYSTEM.Status.DisplayError(qstatus)) }
set rset = tStatement.%Execute()
Set characterList = []
while rset.%Next(){
Set characterInfo = {}
Set characterInfo.Name = rset.Name
set characterInfo.Race = rset.Race
Set characterInfo.Class = rset.Class
Set characterInfo.Level = rset.Level

Do characterList.%Push(characterInfo)

}
if (rset.%SQLCODE < 0) {write "%Next failed:", !, "SQLCODE ", rset.%SQLCODE, ": ", rset.%Message quit}

set totalCount = rset.%ROWCOUNT

// Définit les propriétés status, totalCount et characterList dans l'objet de résultats
Set results.Status = "success"
Set results.TotalCount = totalCount
Set results.CharacterList = characterList


// Renvoyer l'objet de résultats
Quit results
}

C'est notre API! La spécification actuelle ne fournit pas de moyen de mettre à jour ou de supprimer des caractères de la base de données, et cela reste un exercice pour le lecteur!

Configuration IRIS

Maintenant que notre API REST est implémentée, comment pouvons-nous la faire communiquer avec IRIS ? Dans le portail de gestion, si vous allez sur la page Administration du système > Sécurité > Applications > Applications Web, vous pouvez créer une nouvelle application Web. Le nom de l'application est le point de terminaison que vous utiliserez lors de vos requêtes. Par exemple, si vous l'avez nommée /api/TTRPG/, les requêtes pour l'API iront à http://{IRISServer}:{host}/api/TTRPG/{endpoint}. Pour une installation locale de sécurité normale d'IRIS, cela ressemble à http://localhost:52773/api/TTRPG/{endpoint}. Donnez-lui une belle description, définissez l'espace de noms souhaité et cliquez sur le bouton radio pour REST. Pour activer l'authentification JWT, cochez la case "Use JWT Authentication". Le délai d'expiration du jeton d'accès JWT détermine la fréquence à laquelle un utilisateur devra recevoir un nouveau JWT. Si vous prévoyez de tester l'API pendant une période prolongée, je vous recommande de fixer cette valeur à une heure (3 600 secondes) et le délai d'expiration du jeton JWT (JWT Refresh Token Timeout) (la période pendant laquelle vous pouvez renouveler votre jeton avant qu'il n'expire définitivement) à 900 secondes.

web app config

Maintenant que l'application est configurée, nous devons configurer IRIS lui-même pour permettre l'authentification JWT. Vous pouvez configurer cette option dans Administration système > Sécurité > Sécurité du système > Authentification/Options de session Web. Le champ de l'émetteur du JWT et l'algorithme de signature à utiliser pour signer et valider les JWT se trouvent en bas de la page. Le champ émetteur apparaît dans la section de demande d'indemnisation du JWT et a pour but d'indiquer qui vous a donné ce jeton. Vous pourriez le définir sur "InterSystems".

JWT authentication config

Temps de test

Tout est configuré et mis en œuvre, alors lançons-nous ! Chargez votre outil favori de création de requêtes API (j'utiliserai une extension Firefox appelée RESTer dans les exemples) et nous allons construire des requêtes API REST.

Tout d'abord, essayons de répertorier tous les caractères qui existent.

list no token

Nous avons reçu une erreur "HTTP 401 Unauthorized". C'est parce que nous ne sommes pas connectés. Vous vous dites peut-être, Elliott, nous n'avons pas implémenté de fonctionnalité de connexion à cette API REST. Ce n'est pas grave, car InterSystems IRIS s'en charge pour nous lorsque nous utilisons l'authentification JWT. Il fournit quatre points de terminaison que nous pouvons utiliser pour gérer notre session. Il s'agit de /login, /logout /revoke et /refresh. Ils peuvent être personnalisés dans le fichier disp.cls comme dans l'exemple ci-dessous :

Parameter TokenLoginEndpoint = "mylogin";
Parameter TokenLogoutEndpoint = "mylogout";
Parameter TokenRevokeEndpoint = "myrevoke";
Parameter TokenRefreshEndpoint = "myrefresh";

Accédons maintenant au point de terminaison /login.

logging in

Le corps de cette demande n'est pas affiché pour des raisons de sécurité, mais il reprend la structure JSON :

{"user":"{YOURUSER}", "password":"{YOURPASSWORD}"}

En échange de notre mot de passe, nous recevons un JWT! C'est la valeur de "access_token". Nous allons copier ceci et l'utiliser dans nos demandes à l'avenir afin de ne pas avoir à transmettre notre mot de passe tout le temps.

Maintenant que nous avons un JWT pour l'authentification, essayons de créer un caractère!

Nous formatons notre demande comme ci-dessous:

character creation

Utilisation du jeton "bearer token" en tant qu'en-tête au format "Authorization: Bearer {JWTValue}". Dans une requête curl, vous pouvez écrire ceci avec -H "Authorization: Bearer {JWTValue}"

Créons un autre caractère pour le plaisir, en utilisant les valeurs de votre choix.

Essayons maintenant de répertorier tous les caractères qui existent dans la base de données.

listing characters

Nous récupérons nos deux caractères que nous avons créés! Mais qu'en est-il si nous voulons simplement accéder à l'un d'entre eux ? Eh bien, nous avons implémenté cela avec le point de terminaison /characters/{charName}. Nous pouvons formater cette requête comme ceci:

retrieving specific character

C'est notre API REST qui est à l'œuvre ! Lorsque vous avez terminé votre session, vous pouvez vous déconnecter au point de terminaison /logout en utilisant votre JWT. Ceci révoquera le JWT et le mettra sur une liste noire afin que vous ne puissiez plus l'utiliser.

Conclusion

Les versions 2022.2+ d'InterSystems IRIS offrent la possibilité de s'authentifier auprès d'une API REST à l'aide de jetons web (JWT) JSON. Cette fonctionnalité améliore la sécurité en limitant l'utilisation des mots de passe et en définissant une date d'expiration pour l'accès à l'API.

J'espère que cette présentation sur la génération d'une API REST et sa sécurisation avec des JWT via IRIS vous a été utile. Faites-moi savoir si c'était le cas! J'apprécie tout commentaire.

Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer