Comment sécuriser le fournisseur de service web
Un WS est aussi perméable aux attaques informatiques que n'importe quelle page normale du site web. Voyons déjà comment se comporte le serveur REST quand on tente de provoquer des erreurs classiques.
Erreur de controleur
Lors de la définition de notre serveur REST, nous avons créé le controleur rest:
<?php class RestController extends Oft_Controller_Action { public function init() { $this->_helper->layout()->disableLayout(); } /** * Point d'entrée par défaut de l'application */ public function indexAction() { // ce que fait de notre controleur... } }
Si on tente de lancer un contrôleur différent de rest, Zend Framework va réagir en signalant que le contrôleur demandé n'est pas défini.
Donc, coté contrôleur, nous sommes tranquille.
Erreur d'action
Dans notre contrôleur rest, l'action par défaut est index. Exemple de tentative d'appel à une action autre que index:
http://www.mon-serveur.com/rest/getSite/
Ici, l'erreur est de confondre l'action avec la méthode demandée. Zend Framework réagit encore une fois de la bonne manière en indiquant que l'action getSite n'est pas définie.
Donc, coté action, nous sommes également tranquille.
Erreur de méthode
Nous allons d'abord commencer par ne pas appeler de méthode:
http://www.mon-serveur.com/rest
Voici la réponse en XML:
<?xml version="1.0" encoding="UTF-8"?> <rest generator="zend" version="1.0"> <response> <message>No Method Specified.</message> </response> <status>failed</status> </rest>
Le serveur REST renvoie une structure XML avec un status contenant failed. La structure response message indique la nature de l'erreur, ici No Method Specified.
Voyons maintenant le cas d'une méthode vraiment inconnue:
http://www.mon-serveur.com/rest&method=testSite
Voici la réponse en XML:
<?xml version="1.0" encoding="UTF-8"?>
<testSite generator="zend" version="1.0">
<response>
<message>Unknown Method 'testSite'.</message>
</response>
<status>failed</status>
</testSite>
Donc, coté méthode non définie ou inconnue, nous sommes également protégé.
Erreur d'argument
Les arguments sont en fait les paramètres passés par l'URL. Pour notre méthode getSite on attend le paramètre id. Voyons ce qui se passe si on omet l'argument:
http://www.mon-serveur.com/rest&getSite
Voici la réponse en XML:
<?xml version="1.0" encoding="UTF-8"?>
<My_Restserver generator="zend" version="1.0">
<getSite>
<response>
<message>Invalid Method Call to getSite. Missing argument(s): id.</message>
</response>
<status>failed</status>
</getSite>
</My_Restserver>
Voyons maintenant le cas d'un argument inconnu:
http://www.mon-serveur.com/rest&getSite&num=3
La réponse XML sera identique au cas précédent.
donc, coté absence ou erreur de nom d'argument, nous sommes également protégé.
Erreur dans la forme ou le contenu d'un argument
Notre méthode getSite attend un argument id qui doit toujours être un entier numérique. Que se passe-il si on omet cet entier ou si on tente d'y mettre autre chose qu'un entier?
A ce stade, notre serveur REST ne sera plus d'un grand secours. C'est à notre script php de prendre le relais. Si on ne teste pas la validité de notre argument id, nous aurons des erreurs de traitement:
http://www.mon-serveur.com/rest&getSite&id
Va faire planter les requêtes chargées de remonter les données. De même:
http://www.mon-serveur.com/rest&getSite&id=TRUC
qui aura le même effet!
Pour protéger l'argument id, on effectue un test sur sa valeur:
<?php class Pbs_Restserver { /** * Récupère la fiche d'un site * @param int $id id fiche site * @return array Données d'un site */ public function getSite($id) { // si paramètre id est pas numérique, échec recherche if (!is_numeric($id)) { return array('msg' => "erreur id pas entier numérique", 'status' => 'failed'); } // cherche fiche site par id $Site = new Pbs_Model_Sites(); $resultat = $Site->getFicheSiteParId($id); // sinon on renvoie les données trouvées return $resultat; } }
On teste simplement si le paramètre id est bien numérique. Si ce n'est pas le cas, on renvoie un tableau avec deux données:
- msg: le texte du message d'erreur
- status: le texte du statut de notre WS
A partir de maintenant, si on tente d'entrer une valeur autre qu'un entier comme paramètre de id, on aura en retour ce flux XML:
<?xml version="1.0" encoding="UTF-8"?> <My_Restserver generator="zend" version="1.0"> <getSite> <msg>erreur id pas entier numérique</msg> <status>failed</status> </getSite> </My_Restserver>
Rien ne nous interdit de rajouter d'autres données en retour de message d'erreur. Voici le tableau initial de retour d'info:
//... if (!is_numeric($id)) { return array('msg' => "erreur id pas entier numérique", 'status' => 'failed'); } // ... suite script...
on va rajouter une ligne dans ce tableau de sortie:
//... if (!is_numeric($id)) { return array('msg' => "erreur id pas entier numérique", 'info' => "http://www.mon-serveur.com/info/getSite/", 'status' => 'failed'); } // ... suite script...
et le flux XML devient:
<?xml version="1.0" encoding="UTF-8"?> <My_Restserver generator="zend" version="1.0"> <getSite> <msg>erreur id pas entier numérique</msg> <info>http://www.mon-serveur.com/info/getSite/</info> <status>failed</status> </getSite> </My_Restserver>
Ici, à titre d'exemple, on a rajouté un lien renvoyant vers la documentation en ligne de notre méthode getSite.
Vous noterez que nous agissons toujours entre la partie serveur, laquelle est déclarée dans le contrôleur rest et le modèle de notre site. Si vous avez bien programmé les modèles de votre site, vous n'avez nul besoin d'agir en amont sur ces modèles d'accès aux données. Au pire, vous pouvez étendre les fonctions existantes. En procédant ainsi, vous exploitez les mêmes modèles d'accès aux données pour le site web et pour le serveur REST.
Cas des arguments en supplément
Que se passe-t-il si le site distant tente d'entrer des arguments inconnus en supplément des arguments attendus?
http://www.mon-serveur.com/rest&getSite&id=3&key=AZ3DFGH2J
Ici on tente d'insérer un argument key.
Dans ce cas, l'argument est simplement ignoré.
Sécurisation d'accès par clé
Peut-on exploiter cet argument en amont? Oui, en amont de nos méthodes, c'est à dire au niveau de notre contrôleur rest
<?php class RestController { public function init() { // $this->_helper->layout()->disableLayout(); } /** * Point d'entrée par défaut de l'application */ public function indexAction() { $key = $this->getRequest()->getParam('key'); // @TODO: ici traitement de KEY $server = new Zend_Rest_Server(); $server->setClass('My_Restserver'); $server->handle(); } }
On a rajouté cette ligne dans le code du serveur REST:
// .... $key = $this->getRequest()->getParam('key'); // .....
A charge pour vous d'en faire ce que vous souhaitez. Ici, le paramètre key peut être une clé utilisateur, c'est à dire une clé par utilisateur. Vous pouvez vérifier les droits rattachés à l'utilisateur. Incidemment, vous pouvez également contrôler le nombre de requêtes effectuées par utilisateur: par jour, par semaine, par mois, etc...
Nous avons donc là un moyen de contrôler l'utilisation du WS.
Voici justement l'URL d'un site qui demande une clé utilisateur:
http://www.isbndb.com/api/books.xml?access_key=2SZK9FCA&index1=isbn&value1=9782706600586
Ici, la clé est indiquée après le paramètre access_key:
access_key=2SZK9FCA
Le lien URL est bien réel. Nous avons simplement mis ici une clé fictive.
Si vous donnez le contrôle d'accès par clé selon ce moyen, vous pouvez non seulement associer une clé à un utilisateur, mais également stocker l'URL du site qui va appeler votre WS. Voir la variable $_SERVER['HTTP_REFERER']. Ainsi le lien URL avec identification par clé n'est utilisable que sur un serveur précis.
L'API de Google Maps identifie les utilisateurs et les clés d'accès à l'API selon ce moyen. Une clé ne peut être utilisée que sur un site précis. Si vous migrez vos scripts sur un autre serveur, il faudra demander une autre clé pour ce serveur.
Erreur à la restitution de données
Nous avons bien sécurisé notre WS rest. mais que se passe--t-il si notre requête ne remonte pa sde données?
Nous allons agir au niveau de la méthode getSite en rajoutant un test:
// cherche fiche site par id
$Site = new Pbs_Model_Sites();
$resultat = $Site->getFicheSiteParId($id);
// si aucune fiche trouvée erreur de recherche
if(empty($resultat)) {
return array('msg' => "pas de site avec cet id", 'status' => 'failed');
}
// sinon on renvoie les données trouvées
return $resultat;
Notez que ZF insère systématiquement un rapport d'exécution: status avec la valeur success si la transaction a réussi, failed si la transaction a échoué.
Donc, si vous effectuez des tests supplémentaires, il faut toujours transmettre au minimum le tableau avec la paire clé-valeur:
- status => "success"
- status => "failed"
Vous pouvez transmettre d'autres valeurs, à charge pour vous de les gérer. Par exemple, imaginons que dans la base de données que nous interrogeons par WS, il y ait des sites en attente de validation. On pourrait transmettre un rapport qui serait:
- status => "waiting"
pour indiquer au consommateur que l'info existe mais n'est pas validée coté serveur.
En conclusion
Monter un WS en architecture REST à l'aide de Zend Framework est donc très facile. Nous disposons là d'un moyen pratique pour partager des informations avec des systèmes très différents tout en gardant le contrôle sur le partage de ces informations.
Voyons maintenant comment consommer un WS