Skip to content

Sécurité

mekor-dev edited this page Nov 20, 2020 · 11 revisions

Authentification

JWT

L'authentification sur l'API se fait à l'aide de JSON Web Tokens. Un service de l'API permettra d'échanger un couple username / password contre deux token :

  • accessToken : Permet d'accéder aux service requérant d'être authentifié.
  • refreshToken: Permet de récupérer un nouvel accessToken dans le cas ou le notre est expiré.

Plus d'information sur JWT : https://jwt.io/

Librairie utilisée pour créer / parser les JWT : https://github.com/jwtk/jjwt

Récupération des tokens

Requête

Le service suivant permet de récupérer les tokens :

http://host/api/users/authenticate (AccessTokenAPI.authenticate())

La requête devra contenir un header Authorization contenant le couple username / password après les transformations suivantes :

  • concaténation des deux chaines séparé par un ":"
      fred.allen@mail.com:testPassword
  • Encodage en base 64
      ZnJlZC5hbGxlbkBtYWlsLmNvbTp0ZXN0UGFzc3dvcmQ=
  • Préfixage par 'Basic '
      Basic ZnJlZC5hbGxlbkBtYWlsLmNvbTp0ZXN0UGFzc3dvcmQ=

Il est recommandé, pour plus de sécurité, d'encrypter ce header, surtout si vous n'êtes pas en HTTPS.

Réponse

Ce service renverra un objet contenant l'accessToken, le refreshToken et l'utilisateur qui a été authentifié.

{
  "accessToken" : "...",
  "refreshToken" : "...",
  "user" : {"..."}
}
  • 400 : Le header une fois décodé n'est pas au format username:password
  • 401 : L'authentification a échoué. Le header n'est pas présent, ou mal préfixé, ou le couple username / password est invalide.

Joindre l'accessToken aux requêtes.

Un fois l'utilisateur authentifié, il faudra joindre son accessToken dans toutes les prochaines requêtes qu'il fera. Pour cela, il suffit d'ajouter son token dans le header Authorization, prefixé de 'Bearer '

ex : Authorization=Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmcmVkLmFsbGVuQG1haWwuY29tIiwiaWF0IjoxNTQ4OTQ5OTQ1LCJleHAiOjE1NDg5NTE3NDV9.I_TzLTGqnp0S0JAccBR9xmjZvyRbcHPZ-01S_7-H-SA

  • 400 : Header mal formé
  • 401 : Erreur d'authentification

Le processus de vérification est fait par la classe AccessTokenFilter

Un nouvel accessToken est également retourné dans chaque réponse de l'API en header, si l'utilisateur est déjà authentifié, grâce à la classe AccessTokenRefreshFilter

Rafraîchir le token

Requête

Le service suivant permet de récupérer un nouvel accessToken à partir du refreshToken :

http://host/api/users/refresh (AccessTokenAPI.refreshAccessToken())

Il faudra, en body de la requête passer le refreshToken.

Réponse

Ce service renverra un objet contenant l'accessToken et l'utilisateur qui a été authentifié.

{
  "accessToken" : "...",
  "user" : {"..."}
}

Vérification des droits

Annotations par défaut

Les annotations suivantes sont déjà présentes, et pourront être utilisées dans la plupart des projets :

  • @ControlPublic : Accessible à tous
  • @ControlLoggedIn : Accessible aux utilisateurs authentifiés
  • @ControlAdmin : Accessible aux admins
  • @ControlAccessNotification : Vérifie si l'utilisateur authentifié a accès a la notification en pathparam

Le fonctionnement de ces annotations est détaillé dans les chapitres suivants.

Sans paramètre

Par défaut, afin d'éviter les oublis, l'application renvoie une erreur 500 sur toutes les méthodes non protégées de l'API. Pour protéger une méthode, on peut l'annoter avec une annotation que l'on aura créé, et vérifier les droits dans ApiControlFilter.

//Création de l'annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ControlAdmin {
}
//Annotation de la méthode à protéger
@GET
@ControlAdmin
public void adminOnly(){
}
//ApiControlFilter
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
	if (apiUtils.isAnnotationPresent(resourceInfo, ControlAdmin.class)) {
		authorization.isAdmin(); // throw AuthorizationException si l'utilisateur n'est pas admin
	}
}

Avec paramètre

Pour ajouter un paramètre, il suffit d'ajouter le nom du pathParam que l'on veut passer a l'APIControlFilter en tant que paramètre de l'annotation

On crée d'abord notre annotation, contenant un ou plusieurs paramètres :

//Création de l'annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ControlAccessNotification {

   public String notifID();

}

On décore ensuite la méthode à protéger, en pensant à bien ajouter le nom du pathparam :

//Annotation de la méthode à protéger
@GET
@Path("/notifications/{id}")
@ControlAccessNotification(notifID = "id")
public Response put(@PathParam("id") Long id) {
	//process
}

On peut désormais récupérer notre ID de notification dans APIControlFilter :

public void filter(ContainerRequestContext requestContext) throws IOException {
	log.debug("Entering filter()");
	// ControlAccessNotification
	if (apiUtils.isAnnotationPresent(resourceInfo, ControlAccessNotification.class)) {
		ControlAccessNotification annotation = apiUtils.getAnnotation(resourceInfo, ControlAccessNotification.class); // récupération de l'annotation
		Long notifID = Long.parseLong(findPathParamValue(annotation.notifID())); // récupération de la valeur du pathparam
		log.debug("ControlAccessNotification for notifID : {}", notifID);
		authorization.canAccessNotification(notifService.findByIDHandleNotFound(notifID, currentRequest.isAdmin()));
	}
	// No annotation
	else {
		log.error("No control annotation found on the service method");
		throw new AuthorizationException(500, "Error while authorizing access to the service");
	}
	log.debug("Leaving filter()");
}

Comme vous l'avez sûrement remarqué, la méthode magique, c'est findPathParamValue(). Elle va chercher le PathParam qui porte le nom demandé :

private String findPathParamValue(String pathparam) {
	for (Entry<String, List<String>> param : uriInfo.getPathParameters().entrySet()) {
		if (param.getKey().equals(pathparam)) {
			return param.getValue().get(0);
		}
	}

	log.error("Could not find pathParam with given name for notifID param");
	throw new AuthorizationException(500, "Error while authorizing access to the service");
}

Clone this wiki locally