Introduction :
Avant de présenter OAuth 2.0 et OpenID Connect, il est important de clarifier les concepts d'authentification et d'autorisation. L'authentification permet d'identifier un utilisateur en validant ses informations d'identification telles qu'un nom d'utilisateur et un mot de passe. L'autorisation permet de donner accès à des parties spécifiques d'une API à un utilisateur authentifié.
OAuth 2.0 est une norme d'autorisation ouverte largement acceptée qui permet à un utilisateur de donner son consentement pour qu'une application cliente tierce accède à des ressources protégées en son nom. La délégation d'autorisation consiste à donner le droit à une application cliente tierce d'agir au nom de l'utilisateur en appelant une API protégée.
Concepts clés :
- Propriétaire de la ressource : l'utilisateur final.
- Client : l'application cliente tierce qui souhaite appeler des API protégées au nom de l'utilisateur final.
- Serveur de ressources : le serveur qui expose les API à protéger.
- Serveur d'autorisation : le serveur qui délivre des jetons au client une fois que l'utilisateur final a été authentifié. L'authentification des utilisateurs est généralement déléguée à un fournisseur d'identité.
Exemple : Pour donner accès à des API protégées au nom de l'utilisateur, un jeton d'accès est émis par le serveur d'autorisation pour que l'application cliente tierce agisse au nom de l'utilisateur sans avoir à partager ses informations d'identification. Les jetons d'accès représentent un ensemble de droits d'accès limité dans le temps. Un jeton d'actualisation peut également être délivré pour obtenir de nouveaux jetons d'accès sans impliquer l'utilisateur.
Flux d'octroi d'autorisation : La spécification OAuth 2.0 définit quatre flux d'octroi d'autorisation pour l'émission de jetons d'accès. Le flux d'octroi de code d'autorisation est le plus sûr mais aussi le plus complexe. Il exige que l'utilisateur interagisse avec le serveur d'autorisation à l'aide d'un navigateur Web pour s'authentifier et donner son consentement à l'application cliente.

Le flux d'octroi de code d'autorisation :
- Le client (l'application cliente tierce) envoie l'utilisateur au serveur d'autorisation dans le navigateur Web.
- Le serveur d'autorisation authentifie l'utilisateur et demande son consentement.
- Le serveur d'autorisation redirige l'utilisateur vers l'application cliente avec un code d'autorisation qui ne peut être utilisé qu'une seule fois et pendant une courte période.
- L'application cliente échange le code d'autorisation contre un jeton d'accès auprès du serveur d'autorisation en utilisant son ID client et son secret client. Le secret client doit être protégé car il est sensible.
- Le serveur d'autorisation émet un jeton d'accès et éventuellement un jeton d'actualisation.
- L'application cliente peut envoyer une requête à l'API protégée avec le jeton d'accès pour accéder aux ressources.
- Le serveur de ressources valide le jeton d'accès et sert la demande en cas de validation réussie. Les étapes 6 et 7 peuvent être répétées tant que le jeton d'accès est valide. Lorsque la durée de vie du jeton d'accès a expiré, le client peut utiliser son jeton d'actualisation pour obtenir un nouveau jeton d'accès.
Autres flux d'octroi d'autorisation :
- Flux d'octroi implicite : pour les applications clientes qui ne peuvent pas protéger un secret client, le navigateur récupère directement un jeton d'accès auprès du serveur d'autorisation. Ce flux ne permet pas de demander de jeton d'actualisation.
- Flux d'octroi des informations d'identification du mot de passe du propriétaire de la ressource : lorsque l'utilisateur doit partager ses informations d'identification avec l'application cliente pour acquérir un jeton d'accès.
- Flux d'octroi des informations d'identification du client : pour les applications clientes qui n'ont pas de lien avec un utilisateur spécifique et qui utilisent leur propre ID client et secret client pour obtenir un jeton d'accès.
La spécification complète d'OAuth 2.0 est disponible sur https://tools.ietf.org/html/rfc6749. D'autres spécifications détaillent divers aspects d'OAuth 2.0.
OAuth 2.1 est une version consolidée et améliorée d'OAuth 2.0. Les améliorations les plus importantes sont l'intégration de PKCE dans le flux d'octroi de code d'autorisation, l'obsolescence du flux d'autorisation implicite et du flux d'octroi des informations d'identification du mot de passe du propriétaire de la ressource. Seuls le flux d'octroi de code d'autorisation et le flux d'octroi d'informations d'identification client seront utilisés dans ce chapitre. La spécification préliminaire d'OAuth 2.1 est disponible sur https://tools.ietf.org/html/draft-ietf-oauth-v2-1-01.
OpenID Connect
Voici une introduction à OAuth 2.0 et OpenID Connect, qui sont deux normes de sécurité importantes utilisées dans les systèmes informatiques modernes.
OAuth 2.0 permet à un utilisateur de donner à une application tierce le droit d'accéder à des ressources protégées en son nom, sans avoir à partager ses informations d'identification avec l'application. Les jetons d'accès sont émis par un serveur d'autorisation et permettent à l'application d'agir au nom de l'utilisateur en appelant des API protégées. Il existe plusieurs flux d'octroi d'autorisation, tels que le flux d'octroi de code d'autorisation et le flux d'octroi des informations d'identification du client.
OpenID Connect est un module complémentaire à OAuth 2.0 qui permet aux applications clientes de vérifier l'identité des utilisateurs en utilisant un jeton d'identification encodé sous forme de JWT. Les jetons d'identification sont signés numériquement et peuvent contenir des informations telles que l'identifiant et l'adresse e-mail de l'utilisateur.
OIDC définit également des points de terminaison standardisés pour la découverte des URL et l'obtention des clés publiques pour vérifier les JWT signés numériquement. Un point de terminaison d'informations utilisateur permet également d'obtenir des informations supplémentaires sur l'utilisateur authentifié.
OAuth 2.0 et OpenID Connect sont deux normes de sécurité importantes qui permettent de sécuriser les systèmes informatiques modernes en autorisant l'accès aux ressources protégées et en vérifiant l'identité des utilisateurs.
Sécurisation du système
Le paysage du système ressemblera à ce qui suit :

D'après le schéma précédent, on peut remarquer que :
- HTTPS est utilisé pour les communications externes, tandis que HTTP en texte brut est utilisé pour les communications internes au paysage système.
- Le serveur d'autorisation OAuth 2.0 local sera accessible depuis l'extérieur via le serveur périphérique.
- Le serveur périphérique et le microservice product-composite valideront les jetons d'accès en tant que JWT signés.
- Le serveur périphérique et le microservice product-composite obtiendront les clés publiques du serveur d'autorisation à partir de son point de détermination jwk-set, puis les utiliseront pour valider la signature des jetons d'accès basés sur JWT.
- Avec cette vue d'ensemble de la sécurité du paysage système, commençons par voir comment protéger les communications externes contre les interceptions en utilisant HTTPS.
Protection des communications externes avec HTTPS
Dans cette section, nous allons apprendre comment empêcher l'interception clandestine des communications externes, telles que celles provenant d'Internet, via les API publiques exposées par le serveur Edge. Pour cela, nous allons utiliser HTTPS pour chiffrer la communication. Les étapes à suivre sont les suivantes :
- Créer un certificat : nous allons créer notre propre certificat auto-signé qui sera suffisant pour des fins de développement.
- Configurer le serveur Edge : il doit être configuré pour n'accepter que le trafic externe basé sur HTTPS utilisant le certificat.
Voici la commande à utiliser pour créer un certificat auto-signé :
keytool -genkeypair -alias localhost -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore edge.p12 -validity 3650
Pour configurer le serveur edge, les éléments suivants sont ajoutés à application.yml dans le projet gateway:
server.port: 8443
server.ssl:
key-store-type: PKCS12
key-store: classpath:keystore/edge.p12
key-store-password: password
key-alias: localhost
Le chemin d'accès au certificat est spécifié dans le paramètre server.ssl.key-store et est défini sur classpath:keystore/edge.p12. Cela signifie que le certificat sera récupéré depuis le classpath à partir de l'emplacement keystore/edge.p12.
Le mot de passe du certificat est spécifié dans le paramètre server.ssl.key-store-password.
Pour indiquer que le serveur Edge utilise HTTPS au lieu de HTTP, nous modifions également le port de 8080 à 8443 dans le paramètre server.port.
En plus de ces changements dans le serveur Edge, des modifications doivent également être apportées aux fichiers suivants pour refléter les changements de port et de protocole, en remplaçant HTTP par HTTPS et 8080 par 8443 dans les fichiers docker-compose.
Remplacement d'un certificat auto-signé lors de l'exécution
Inclure un certificat auto-signé dans le fichier .jar est utile uniquement pour le développement. Pour les environnements de test et de production, il est nécessaire d'utiliser des certificats émis par des autorités de certification de confiance.
Il est également important de pouvoir spécifier les certificats à utiliser pendant l'exécution sans avoir à reconstruire les fichiers .jar ou l'image Docker. Lorsque Docker est utilisé, nous pouvons mapper un volume dans le conteneur Docker vers un certificat situé sur l'hôte Docker. Nous pouvons également configurer des variables d'environnement pour le conteneur Docker qui pointent vers le certificat externe dans le volume Docker.
Pour remplacer le certificat empaqueté dans le fichier .jar, procédez comme suit :
- Créez un deuxième certificat et définissez le mot de passe sur "testtest" lorsqu'on vous le demande :
mkdir keystore
keytool -genkeypair -alias localhost -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore/edge-test.p12 -validity 3650
- Mettez à jour le fichier Docker Compose,
docker-compose.yml, avec des variables d'environnement pour l'emplacement et le mot de passe du nouveau certificat, ainsi qu'un volume mappé vers le dossier dans lequel le nouveau certificat est placé. La configuration du serveur Edge ressemblera à ceci après les modifications :
gateway:
environment:
- SPRING_PROFILES_ACTIVE=docker
- SERVER_SSL_KEY_STORE=file:/keystore/edge-test.p12
- SERVER_SSL_KEY_STORE_PASSWORD=testtest
volumes:
- $PWD/keystore:/keystore
build: spring-cloud/gateway
mem_limit: 512m
ports:
- "8443:8443"
- Si le serveur Edge est opérationnel, il doit être redémarré avec les commandes suivantes :
docker-compose up -d --scale gateway=0
docker-compose up -d --scale gateway=1
Ceci conclut la section sur la protection des communications externes avec HTTPS. Dans la prochaine section, nous allons apprendre comment sécuriser l'accès au serveur de découverte, Netflix Eureka, en utilisant l'authentification HTTP Basic.
Ajout d'un serveur d'autorisation local
Afin de pouvoir exécuter des tests locaux et entièrement automatisés avec des API sécurisées en utilisant OAuth 2.0 et OpenID Connect, nous allons ajouter un serveur d'autorisation conforme à ces spécifications à notre paysage système. Spring Security ne fournit malheureusement pas de serveur d'autorisation prêt à l'emploi. Cependant, en avril 2020, un projet communautaire dirigé par l'équipe Spring Security, appelé Spring Authorization Server, a été annoncé dans le but de fournir un serveur d'autorisation. Pour plus d'informations, consultez le lien suivant : https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server.
Le serveur d'autorisation Spring prend en charge l'utilisation du point de terminaison de découverte OpenID Connect et la signature numérique des jetons d'accès. Il fournit également un point de terminaison accessible à l'aide des informations de découverte pour obtenir des clés permettant de vérifier la signature numérique d'un jeton. Avec la prise en charge de ces fonctionnalités, il peut être utilisé comme serveur d'autorisation dans des tests locaux et automatisés qui vérifient que le paysage système fonctionne comme prévu.
Le serveur d'autorisation de ce livre est basé sur l'exemple de serveur d'autorisation fourni par le projet Spring Authorization Server. Les modifications suivantes ont été apportées à l'exemple de projet :
- Le fichier de construction a été mis à jour pour suivre la structure des fichiers de construction des autres projets dans ce livre.
- Le port a été défini sur 9999.
- Un Dockerfile a été ajouté avec la même structure que pour les autres projets de ce livre.
- Le serveur d'autorisation a été intégré à Eureka pour la découverte de services de la même manière que les autres projets de ce livre.
- L'accès public a été ajouté aux terminaux de l'actionneur.
Pour intégrer le serveur d'autorisation dans l'infrastructure système, des modifications ont été apportées aux fichiers suivants :
- Le serveur a été ajouté au fichier de construction commun,
settings.gradle. - Le serveur a été ajouté aux trois fichiers Docker Compose,
docker-compose*.yml. - Le serveur Edge,
spring-cloud/gateway:- Une vérification de l'état a été ajoutée pour le serveur d'autorisation dans
HealthCheckConfiguration. - Les routes vers le serveur d'autorisation pour les URI commençant par
/oauth,/loginet/erroront été ajoutées dans le fichier de configurationapplication.yml. Ces URI sont utilisés pour émettre des jetons pour les clients, authentifier les utilisateurs et afficher des messages d'erreur. - Étant donné que ces trois URI doivent être non protégés par le serveur Edge, ils sont configurés dans la nouvelle classe
SecurityConfigpour autoriser toutes les requêtes.
- Une vérification de l'état a été ajoutée pour le serveur d'autorisation dans
Maintenant que le serveur d'autorisation a été ajouté au paysage système, passons à la section suivante et voyons comment utiliser OAuth 2.0 et OpenID Connect pour authentifier et autoriser l'accès aux API.
Protection des API à l'aide d'OAuth 2.0 et d'OpenID Connect
Maintenant que le serveur d'autorisation est en place, nous pouvons améliorer le serveur périphérique et le service product-composite pour qu'ils deviennent des serveurs de ressources OAuth 2.0, de sorte qu'ils nécessitent un jeton d'accès valide pour autoriser l'accès. Le serveur périphérique sera configuré pour accepter tout jeton d'accès qu'il peut valider à l'aide de la signature numérique fournie par le serveur d'autorisation. Le service product-composite exigera également que le jeton d'accès contienne des champs d'application OAuth 2.0 valides :
- Le champ d'application
product:readsera requis pour accéder aux API en lecture seule. - Le champ d'application
product:writesera requis pour accéder aux API de création et de suppression.
Le service product-composite sera également amélioré avec une configuration qui permet à son composant d'interface utilisateur Swagger d'interagir avec le serveur d'autorisation pour émettre un jeton d'accès. Cela permettra aux utilisateurs de la page Web Swagger UI de tester l'API protégée.
Les dépendances Spring Security doivent être ajoutées au fichier build.gradle pour que les serveurs de ressources OAuth 2.0 soient pris en charge.
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-oauth2-resource-server'
implementation 'org.springframework.security:spring-security-oauth2-jose'
Des configurations de sécurité ont été ajoutées aux nouvelles classes SecurityConfig dans les deux projets :
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain springSecurityFilterChain(
ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/actuator/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
Les explications pour le code source précédent sont les suivantes :
- L'annotation
@EnableWebFluxSecurityactive la prise en charge de Spring Security pour les API basées sur Spring WebFlux. .anyExchange().authenticated()garantit que l'utilisateur est authentifié avant d'être autorisé à accéder à toutes les autres URL, y compris les points de terminaison Actuator qui ne sont pas protégés. Il faut donc faire attention aux URL exposées sans protection. Par exemple, les points de terminaison Actuator doivent être protégés avant de passer en production..oauth2ResourceServer().jwt()spécifie que l'autorisation sera basée sur des jetons d'accès OAuth 2.0 encodés en tant que JWT.- Le point de terminaison de découverte OIDC du serveur d'autorisation a été enregistré dans le fichier de configuration
application.yml:
app.auth-server: localhost
spring.security.oauth2.resourceserver.jwt.issuer-uri: http://${app.auth-server}:9999
---
spring.config.activate.on-profile: docker
app.auth-server: auth-server
Modifications du service composite
En plus des changements communs appliqués dans la section précédente, les modifications suivantes ont également été appliquées au service composite :
- La configuration de sécurité dans la classe
SecurityConfiga été affinée en exigeant des champs d'application OAuth 2.0 dans le jeton d'accès afin d'autoriser l'accès :
.pathMatchers(POST, "/product-composite/**")
.hasAuthority("SCOPE_product:write")
.pathMatchers(DELETE, "/product-composite/**")
.hasAuthority("SCOPE_product:write")
.pathMatchers(GET, "/product-composite/**")
.hasAuthority("SCOPE_product:read")
Une méthode, logAuthorizationInfo(), a été ajoutée pour consigner les parties pertinentes du jeton d'accès codé JWT à chaque appel à l'API. Le jeton d'accès peut être acquis en utilisant la norme Spring Security, SecurityContext, qui, dans un environnement réactif, peut être acquis à l'aide de la méthode d'assistance statique, ReactiveSecurityContextHolder.getContext(). Reportez-vous à la classe ProductCompositeServiceImpl pour plus de détails.
L'utilisation d'OAuth a été désactivée lors de l'exécution de tests d'intégration basés sur Spring. Pour empêcher la machinerie OAuth de se déclencher lorsque nous exécutons des tests d'intégration, nous la désactivons comme suit :
- Une configuration de sécurité,
TestSecurityConfig, est ajoutée pour être utilisée lors des tests. Il permet d'accéder à toutes les ressources :
http.csrf().disable().authorizeExchange().anyExchange().permitAll();
Dans chaque classe de test d'intégration Spring, nous configurons TestSecurityConfig pour remplacer la configuration de sécurité existante par les éléments suivants :
@SpringBootTest(
classes = {TestSecurityConfig.class},
properties = {"spring.main.allow-bean-definition-
overriding=true"})




