Utiliser les rôles d’Azure Active Directory avec une Web API sous Spring Boot pour authentifier les utilisateurs
Nous avons vu dans un précédent article comment affecter des rôles applicatifs à des groupes d’utilisateurs dans Azure Active Directory. Maintenant nous allons voir de quelle manière ces rôles peuvent être utilisés pour filtrer les utilisateurs dans une API Java sous Spring Boot.
Pour rappeler le contexte, nos API Java utilisent le workflow de code d’autorisation du protocole OAuth 2.0 (définit ici : OAuth 2.0 RFC 6749, section 4.1) pour authentifier les utilisateurs. Le but de cet article est de montrer de quelle manière notre API Java doit être configurée pour vérifier la demande d’accès, puis de contrôler que le workflow d’authentification se déroule de la manière souhaitée.
Nous allons premièrement déployer une API Java sécurisée à l’aide de Spring Security, puis nous simulerons les appels REST d’une application Web à l’aide de l’outil Postman, qui permettra d’effectuer l’ensemble des étapes de validation du workflow OAuth 2.0.
Préparation de l’API Java
Je pars du principe que vous avez déjà préparé la configuration des applications sous Azure Active Directory et que vous avez enregistré sur votre compte locataire Azure deux applications : WebApp et WebAPI, qui vont nous servir tout au long de cet article. Dans le cas contraire, je vous invite à lire et suivre la configuration proposée par l’article suivant.
Passons au code de gestion à fournir dans notre API Java pour la prise en charge du protocole OAuth 2.0 via Azure Active Directory.
Microsoft fournit un SDK pour la prise en charge d’Azure AD pour les applications Java. Vous y trouverez également plusieurs exemples d’intégration sous Spring Boot : https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/spring/azure-spring-boot-samples
Nous partons du principe que vous utilisez Maven pour gérer vos projets Java.
Créez un nouveau projet Maven et ajoutez les dépendances suivantes dans votre pom.xml :
<!-- spring boot starter dependencies. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Azure AD starter dependency. -->
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>azure-spring-boot-starter-active-directory</artifactId>
</dependency>
Ici, nous allons utiliser la version 3.0.0 de la dépendance fournit par Microsoft. Sur le web vous allez rencontrer un grand nombre d’articles sur le sujet mais la plupart du temps les composants utilisés sont dépréciés avec cette nouvelle version, notamment l’utilisation de la classe AADAppRoleStatelessAuthenticationFilter
qui utilise une configuration de sécurité qui n’est plus recommandée par Microsoft.
Attention, la version 3.0.0 d’azure-spring-boot-starter-active-directory
requière une version de Spring Boot égale ou supérieur à 2.0.0.RELEASE et strictement inférieure à 2.4.0.M1.
Notre exemple Java s’appuis sur l’un des exemples fournit par Microsoft pour la création d’un serveur de ressource, à ceci prêt que nous allons le modifier de manière à prendre en compte les rôles utilisateurs comme filtre d’accès et non les scopes (actuellement configurés par défaut par la dépendance).
La dépendance spring-boot-starter-oauth2-resource-server
s’appuis sur Spring Security, nous allons donc passer par une surcharge de WebSecurityConfigurerAdapter
pour appliquer la configuration qui nous intéresse :
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class AADOAuth2ResourceServerSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().authenticated())
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(new AADJwtBearerTokenAuthenticationConverter("roles", "ROLE_"));
}
}
Lors de la réception du token d’autorisation au format JWT (JSON Web Token) par notre API Java, le composant de Microsoft le décortique de manière à extraire le contenu de l’attribut scp
(pour scopes). La gestion des droits pour un objet Principal
Java, qui contient le contexte d’un utilisateur, passe par la collection GrantedAuthority
. Elle est alors remplie avec les informations contenues dans scp
. C’est celle-ci qui sert ensuite à délimiter les droits d’accès aux contrôleurs par l’intermédiaire de l’annotation @PreAuthorize
. Nous allons donc indiquer au convertisseur de parser non pas l’attribut scp
mais l’attribut roles
de note token JWT, et lui associer comme préfixe d’identification ROLE_
à la place de SCOPE_
.
Les contrôleurs de votre API peuvent ensuite être annotés de la manière suivante :
@GetMapping("user")
@PreAuthorize("hasRole('ROLE_User')")
public AADOAuth2AuthenticatedPrincipal getUserPrincipal(Authentication authentication) {
LOGGER.info("User has access");
return (AADOAuth2AuthenticatedPrincipal) authentication.getPrincipal();
}
Enfin nous devons déclarer certaines informations dans le fichier d’environnement application.yml :
azure:
activedirectory:
client-id: <Replace-with-your-WebApp-Client-ID>
app-id-uri: <Replace-with-your-WebApi-URI-application-ID>
Remplacez les valeurs entre <…> par celles déclarées respectivement dans vos deux applications sous Azure AD. Ces paramètres seront transmit à Azure AD pour authentifier votre API à l’aide des endpoints préenregistrés dans la dépendance azure-spring-boot-starter-active-directory
et pour valider le token réceptionné.
Demande d’un jeton et accès à l’API
Voilà, nous y sommes. Nous allons pouvoir nous authentifier auprès d’Azure Active Directory via notre Web App en utilisant le protocole OAuth 2.0, et venir consommer les contrôleurs exposés et sécurisés par rôle utilisateur de notre API Java.
Comme mentionné plus haut nous allons simuler notre application Web depuis l’application Postman pour émettre une demande de jeton. Si vous n’êtes pas habitué à utiliser cet outil je vous invite à consulter la documentation ou à utiliser votre outil de prédilection pour les appels REST.
Vous devez bien entendu exécuter en arrière plan votre API Java sous Maven de manière à lancer un serveur Tomcat. Votre API devrait alors être accessible depuis l’URL http://localhost:8080
ou tout autre port que vous aurez paramétré depuis votre fichier d’environnement.
Direction Postman et faites une nouvelle requête de type Get
. Postman propose d’automatiser la demande de jeton JWT en effectuant les différentes tâches d’acquisition auprès d’Azure AD, c’est à dire la demande d’un code d’autorisation auprès d’Azure AD en vous connectant avec le compte d’un utilisateur certifié, puis l’échange de ce code contre un jeton JWT qui sera utilisé pour vous authentifier auprès de la Web API.
Si nous devions reprendre le schéma du workflow d’indentification disponible dans l’article sur la configuration d’Azure, Postman engloberait les étapes suivantes :
En production, si vous utilisez une SPA du type Angular ou React, ces tâches peuvent être automatisées à l’aide d’une librairie Javascript que Microsoft met à disposition: MSAL.js.
Pas besoin ici de développer une application Web pour effectuer ces tâches. Le but est de vérifier que les étapes 9 et 10 liées à notre API Web se déroulent correctement.
- Cliquez sur l’onglet Authorization
- Sélectionnez dans le champ TYPE la valeur OAuth 2.0
- Sélectionnez dans le champ Add authorization data to la valeur Request Headers
- Dans la zone de droite déployez le sous menu Configure New Token
Il va falloir maintenant remplir un certain nombre de champs à l’aide d’informations provenant du portail Azure AD de votre application WebApp.
- Vous pouvez mettre ce que vous voulez dans le champ Token Name, cela n’a pas d’importance
- Positionnez le champ Grant Type sur Authorization Code
- Le champ Callback URL doit être identique à la valeur que vous avez entré pour l’URI de redirection dans l’onglet Authentification de votre application WebApp.
- Ne cochez pas Authorize using browser. Cette option n’est pas utile ici.
Les deux URL qui suivent correspondent aux endpoints fournit par Azure AD. Elles sont accessibles depuis la vue d’ensemble de l’application WebApp en cliquant sur le bouton Points de terminaison.
- Si vous avez suivi comme il faut l’article sur la configuration des rôles dans Azure, Auth URL doit être de la forme
https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize
oùtenant-id
correspond à votre ID de locataire. Cette URL peut changer en fonction du type d’accès que vous avez autorisé à vos API lors de leur création. - Access Token URL doit être de la forme
https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
oùtenant-id
correspond à votre ID de locataire. - Dans le champ Client ID mettez celui de votre application WebApp
- Dans le champ Client Secret, copiez le secret que vous avez généré depuis l’onglet Certificats et secrets depuis votre application WebApp
- Le champ Scope doit contenir l’URI suivante :
<URI ID d’application de WepAPI>/.default
où.default
représente l’ensemble des scopes exposés par la Web API. Nous aurions très bien pu limiter au seul scope que nous avions déclaré, c’est à direUser.Read
et écrire l’URI de cette manière :<URI ID d’application de WepAPI>/User.Read
- Laissez le champ State vide.
- Sélectionnez Send as Basic Auth header dans le champ Client Authentication
- Cliquez sur le bouton Get New Access Token
Si tout c’est bien passé, vous devriez voir apparaître une popup vous demandant de vous identifier sur la plateforme de Microsoft ainsi qu’un consentement éventuel (si vous n’aviez pas encore consenti à l’accès du scope) pour accéder aux ressources demandées par WebApp.
Utilisez un compte utilisateur que vous avez ajouté au groupe User. Une fois identifié, Postman devrait afficher un récapitulatif du token JWT obtenu. Pensez à cliquer sur le bouton Use Token en haut à droite pour ajouter le token dans le champ Access Token.
Vous avez la possibilité de contrôler le contenu du JWT que vous avez obtenu d’Azure AD en copiant le token dans le lien suivant : https://jwt.ms/
Il ne vous reste plus maintenant qu’à émettre votre requête vers votre API Java en mettant l’URL http://localhost:8080/user
dans la zone Enter request URL de Postman et de cliquer sur Send.
Vous devriez normalement avoir un retour de votre API avec un statut HTTP du type 200 OK
et le contenu des données émises par le contrôleur dans le corps du message. Le token que vous avez obtenu a une période de validité qui vous donne droit d’accéder à la ressource, même si vous décidez de retirer la personne du groupe d’utilisateur. Pour tester que la gestion des rôles fonctionne, retirez la personne du groupe, refaite une demande de token et émettez la vers l’API. Cette fois le statut HTTP de la réponse devrait être du type 403 Forbidden
. Ou 401 Unauthorized
si votre token a expiré entre temps.
Si vous rencontrez des erreurs lors de l’envoi de la requête, n’hésitez pas à ouvrir la console de Postman pour vérifier le contenu de la réponse, vous y trouverez généralement l’exception émise par l’API.
Conclusion
Dans cet article nous avons vu :
- La création d’une API Java utilisant la dépendance azure-spring-boot-starter-active-directory pour la prise en charge des jetons JWT en provenance d’Azure AD utilisant le protocole OAuth 2.0
- La simulation d’une Web App avec Postman pour l’obtention et l’émission d’un jeton JWT vers une Web API Java
Aller plus loin
Maintenant que nous savons comment consommer une API à l’aide des rôles utilisateurs fournis par Azure il serait intéressant de voir comme tout cela peut être mis en œuvre dans un cas réel de production.
Dans l’article suivant nous verrons comment dockeriser nos API Web, de quelle manière elles peuvent être placées derrière un proxy NGINX pour limiter les problèmes de CORS, le tout appelé par une SPA développé en vue.js :
Ressources
- Comment affecter des rôles applicatifs à des groupes d’utilisateurs
- Dépôt GitHub de l’API Java de cet article : https://github.com/GutsFun/AAD-Spring-Boot-Resource-Server
- Dépôt du SDK Java Azure Active Directory : Azure/azure-sdk-for-java
- Dépôt des exemples Microsoft d’implémentation sous Spring Boot : https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/spring/azure-spring-boot-samples
- Documentation de Postman : https://learning.postman.com/