• 8 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 16/11/2023

Configurez OAuth 2.0 avec OpenID Connect sur une application web Spring

Nous allons maintenant travailler sur la partie la plus importante de notre formulaire de connexion : nous allons ajouter une authentification OAuth à l'application web.

Mettez en place un formulaire de connexion pour travailler avec OAuth 2.0 et OIDC

Retrouvez moi dans la démonstration suivante pour configuration une connexion avec OAuth 2.0 et GitHub :

Vous avez du pain sur la planche, mais vous pouvez le faire ! Vous avez déjà créé un formulaire avec la méthode  formLogin()  et une authentification in-memory.

Vous avez également eu recours à l'annotation @Configuration, aux classes SecurityFilterChain et HttpSecurity pour votre formulaire de connexion basique avec Spring Security. Il n’y a pas beaucoup de différences avec la manière dont vous commencerez votre formulaire de connexion avec les nouveaux paramètres de sécurité.

Pour utiliser notre application web client avec une connexion GitHub, vous aurez besoin de vous enregistrer sur GitHub pour obtenir une client id et un client secret. Ces deux appellations font référence aux traditionnels nom d’utilisateur et mot de passe pour votre application web ; c'est ce qui vous permet de vous connecter au serveur d’autorisation de GitHub avec OAuth 2.0.

Cette capture d'écran montre les champs à remplir obligatoirement : le nom de l'application, l'URL de la page d'accueil et un URL de callback. Il y a un bouton pour confirmer l'enrgistrement.
Page d’enregistrement de GitHub OAuth 2.0

Si tout fonctionne, vous obtiendrez vos propres client id et client secret. Mettez-les de côté pour plus tard.

Cette capture d'écran récapitule l'enregistrement avec des options supplémentaires : on voit notamment la possibilité d'ajouter un logo. Au bas de la page il y a un bouton sur lequel il faut cliquer pour confirmer la mise-à-jour des informations du c
Configuration GitHub Auth 2.0

Une fois que vous avez obtenu vos client id et client secret, vous pouvez les renseigner votre fichier application.properties sous le dossier src/java/resources. Cela dirigera directement les utilisateurs OAuth vers le serveur d’autorisation approprié.

Pour définir GitHub en tant que page de connexion OAuth 2.0, vous pouvez copier-coller les lignes ci-dessous dans votre fichier application.properties, mais assurez-vous de remplacer client id et client secret par vos propres informations de connexion générées par GitHub !

spring.security.oauth2.client.registration.github.client-id=<your client ID>

spring.security.oauth2.client.registration.github.client-secret=<client-secret>

Une fois ces lignes collées, votre fichier application.properties devrait ressembler à ça :

Cette capture d'écran montre les propriétés appliquées au client ID et client secret.
Identifiants GitHub dans application.properties

Configurez OAuth 2.0 dans votre page de connexion

Maintenant, ajoutons manuellement la connexion par défaut de OAuth 2.0 à votre page. Vous avez simplement à ajouter la méthode  oauth2login(Customizer.withDefaults())  à votre chaîne de filtres de sécurité dans votre fichier SpringSecurityConfig.java.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	return http.authorizeHttpRequests(auth -> {
		auth.requestMatchers("/admin").hasRole("ADMIN");
		auth.requestMatchers("/user").hasRole("USER");
		auth.anyRequest().authenticated();
	}).formLogin(Customizer.withDefaults()).oauth2Login(Customizer.withDefaults()).build();
	}

Ajoutons une page d'accueil pour GitHub à votre contrôleur, à l'aide de la méthode  getGithub() :

package com.openclassrooms.controllers;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {

	@GetMapping("/user")
	public String getUser() {
		return "Welcome, User";
	}
	
	@GetMapping("/admin")
	public String getAdmin() {
		return "Welcome, Admin";
	}
	
	@GetMapping("/")
	public String getGitHub() {
		return "Welcome, GitHub user";
	}

}

À présent, la mention “Welcome, GitHub user” (Bienvenue, utilisateur GitHub) devrait apparaître après l’authentification avec GitHuB lors de la consultation de la page http://localhost:8080/.

Connectez-vous avec OAuth 2.0

Eh bien… voilà qui est intéressant :

Cette capture d'écran montre le formulaire de connexion par défaut (identifiant et mot de passe) et sous le bouton pour se connecter on voit que l'on peut également se connecter via GitHub en cliquant sur un lien.
Formulaire de connexion par défaut de OAuth 2.0 avec GitHub

En supplément du formulaire de connexion Spring Security, vous disposez à présent du formulaire de connexion par défaut de OAuth 2.0.

Maintenant, cliquez sur le lien du serveur d’autorisation de GitHub

Cette capture d'écran montre le formulaire de connexion pour GitHub, on où on peut rentrer un identifiant et un mot de passe GitHub.
Formulaire d'authentification de OAuth 2.0 GitHub

Cette page va vous rediriger vers un lien spécifique de votre application web. J’ai intitulé le mien OAuth2OpenClassrooms lorsque j’ai enregistré mon application web sur GitHub. Connectez-vous à l’aide de vos identifiants GitHub. 

Cette capture montre que GitHub donne les autorisations à OpenClassrooms.
Autoriser les données personnelles depuis GitHub

Vous devriez arriver sur la page d’accueil.

Explorez le Principal User

Personnalisons votre mot de bienvenue avec le nom d’utilisateur.

Comment s’y prendre ?

Commencez par jeter un œil aux informations transmises par GitHub, et voyez ce que vous pouvez extraire.

Les ressources protégées peuvent être, par exemple, des informations spécifiques concernant l’utilisateur et votre token d’accès. Les ressources non protégées peuvent être votre nom d’utilisateur, dans le but de le rendre disponible plus facilement. Dans GitHub, les ressources non protégées détiennent un grand nombre d’informations. Voyons ça !

Définissons un paramètre de type Principal et de nom user dans la méthode getGithub(). Généralement, on peut récupérer le nom d'utilisateur à partir d'un objetPrincipal en utilisant la méthode user.getName() .

Cela devrait vous indiquer Welcome, <my GitHub username>.

public String getGithub(Principal user){ 
   return "Welcome, " + user.getName(); 
   }

Ça n'a pas l’air de fonctionner… Une donnée numérique apparaît :

Le résultat user.getName()  depuis le principal utilisateur.
Le résultat user.getName() depuis le principal utilisateur.

Voyons ce qu’on peut extraire de l'objet Principal user.

Ajoutez une ligne à la méthode  getGithub()  pour vérifier ce qui nous a été envoyé via l’objet  Principal user. Pour ce faire, renvoyez l’objet  Principal user  converti  en String  grâce à la méthode  user.toString()  .

Vous êtes sûr que le nom d’utilisateur va s’afficher avecuser.toString() ?

C’est ce qu’on va vérifier.

public String getGithub(Principal user){ 
   return user.toString(); 
   }

Cette fois-ci, la page d’accueil de getGitHub a l’air complètement différente. Regardez ça :

Impression de GitHub depuis l’objet principal utilisateur
Impression de GitHub depuis l’objet Principal user

L’interface montre les informations du profil d’utilisateur que j’ai autorisées lorsque je me suis connecté à mon application GitHub OAuth 2.0 au cours de l’authentification. C’est le scope. Vous vous souvenez ? Il s’agit d'informations de profil en lecture seule dont l’application web client dispose, suite à votre autorisation. 

Obtenez des données protégées depuis le Principal user

  1. Je souhaite afficher mon nom complet ainsi que mon adresse mail. J’ai aussi envie de savoir à quoi ressemble mon token d’accès

  2. De plus, je veux que cette méthode soit directement prête à l’utilisation pour d’autres fournisseurs d'identité OAuth 2.0, et pas uniquement GitHub.

Il faut tout d’abord supprimer l’intégralité de la méthode  getGithub()  de la classe LoginController.java. Vous pouvez le faire car vous allez travailler avec des ressources protégées, comme le token d’accès, et l’ID Base64. La classe d’authentification ne sera pas utile à la saisie de ces tokens protégés JWT.

Vous avez besoin d’une nouvelle méthode afin que votre classe LoginController fonctionne pour  la connexion avec OAuth 2.0 mais aussi pour la connexion avec le formulaire. 

Appelez cette méthode getUserInfo() pour lui attribuer un nom générique qui s’applique à plusieurs fournisseurs d'identité, et non uniquement à GitHub. Le Principal reste votre unique paramètre, contenant les informations de l’utilisateur, envoyé par les serveurs d’autorisation. 

@GetMapping("/*")
public String getUserInfo(Principal user) {
}

Ici, Il va vous falloir trouver un moyen de mettre les attributs des utilisateurs en mémoire et ensuite les récupérer. Il existe plusieurs manières de travailler avec le contenu d’une chaîne de caractères.

En l'occurrence, j’utilise la classe StringBuffer avec une instance nomméeuserinfo , car c’est une manière propre de créer des données avec des chaînes de caractères, et d’y ajouter d’autres attributs d’utilisateur. 

public String getUserInfo(Principal user) {
  StringBuffer userInfo= new StringBuffer(); 
  return userInfo.toString();
}

C’est le moment d’ajouter votre nom intégral, votre adresse mail et les informations de votre token d’accès à votre StringBuffer dans la méthodegetUserInfo() .

Pour cela, mettez en place deux autres méthodes ( getUsernamePasswordLoginInfo()  et  getOAuth2LoginInfo()) pour obtenir ces informations. Celles-ci vont retourner un objet de type   StringBuffer  .

Pour la première méthode  getUsernamePasswordLoginInfo() , la classe UsernamePasswordAuthenticationToken permettra de récupérer le nom de l’utilisateur, après avoir vérifié que le token est authentifié, en utilisant la méthode  getPrincipal()  .

Ensuite, cette information sera ajoutée à l’instance du StringBuffer nommée usernameInfo .

Vous pouvez ajouter cette méthode sous la méthode getUserInfo() . 

private StringBuffer getUsernamePasswordLoginInfo(Principal user)
   {
      StringBuffer usernameInfo = new StringBuffer();
      
      UsernamePasswordAuthenticationToken token = ((UsernamePasswordAuthenticationToken) user);
      if(token.isAuthenticated()){
         User u = (User) token.getPrincipal();
         usernameInfo.append("Welcome, " + u.getUsername());
      }
      else{
         usernameInfo.append("NA");
      }
      return usernameInfo;
   }

Lorsque vous vous connecterez avec le formulaire Spring Security, c’est ce précédent code qui s'exécutera.

Dans l’autre méthode nommée  getOauth2LoginInfo() , déclarez une variable intitulée authToken de type OAuth2AuthenticationToken et castez l’objet   Principal user  dans cette nouvelle variable.

La classe OAuth2AuthenticationToken contient des méthodes à utiliser pour des ressources protégées, comme celles contenues dans l’objet  user  .

private StringBuffer getOauth2LoginInfo(Principal user){
   StringBuffer protectedInfo = new StringBuffer();
   OAuth2AuthenticationToken authToken = ((OAuth2AuthenticationToken) user);
}

Grâce à la classe OAuth2AuthenticationToken, l’application client a la permission d’accéder à davantage de ressources protégées, comme le token d‘accès. 

Mais avant cela, créez une variable privée pour votre classe LoginController de type OAuth2AuthorizedClientService, et intitulez-la  authorizedClientService  :

private final  OAuth2AuthorizedClientService  authorizedClientService;

Créez un constructeur public pour votre classe LoginController pour l’injection de la dépendance :

public LoginController(OAuth2AuthorizedClientService authorizedClientService) {
   this.authorizedClientService = authorizedClientService;
}

Revenez dans la méthode  getOauth2LoginInfo()  et récupérez l’instance de  l’objet OAuth2AuthorizedClient. Vous remarquerez que la méthode loadAuthorizedClient retourne le client qui correspond à l’ID et au nom du principal transmis en paramètre.

Pour le moment, la méthode renvoie protectedInfo, qui ne contient aucune donnée.

private StringBuffer getOauth2LoginInfo(Principal user){
   StringBuffer protectedInfo = new StringBuffer();
   OAuth2AuthenticationToken authToken = ((OAuth2AuthenticationToken) user);
OAuth2AuthorizedClient authClient = this.authorizedClientService.loadAuthorizedClient(authToken.getAuthorizedClientRegistrationId(), authToken.getName());
   return protectedInfo;
}

De retour dans la méthode  getUserInfo()  , il faut extraire les informations souhaitées de l’objet Principal user grâce aux 2 méthodes précédemment créées puis les ajouter dans le StringBuffer.

public String getUserInfo(Principal user) {
  StringBuffer userInfo= new StringBuffer();
   if(user instanceof UsernamePasswordAuthenticationToken){
      userInfo.append(getUsernamePasswordLoginInfo(user));
    } else if(user instanceof OAuth2AuthenticationToken){
      userInfo.append(getOauth2LoginInfo(user));
       }
    return userInfo.toString();
   }

Désormais, vous disposez des informations nécessaires pour aller plus loin et afficher le nom complet, l’adresse mail et le token d’accès dans le cas d’une connexion OAuth2.

Utilisez une variable HashMap userAttributes pour récupérer l'ensemble des attributs.

Revenez à votre méthode  getOauth2LoginInfo()   , et ajoutez dans la variable  protectedInfo  les informations de l’utilisateur lors de sa connexion. Le nom et le mail sont des attributs qui sont extraits du principal. Tandis que pour le token d’accès, ajoutez-le en utilisant les méthodes  getAccessToken()  et  getTokenValue()  .

La variable  userToken se voit attribuer la valeur du token d’accès. Elle contiendra donc la chaîne de caractères encodés du token d’accès. Tout cela est ensuite ajouté à  protectedInfo .

private StringBuffer getOauth2LoginInfo(Principal user){

   StringBuffer protectedInfo = new StringBuffer();
   
   OAuth2AuthenticationToken authToken = ((OAuth2AuthenticationToken) user);
   OAuth2AuthorizedClient authClient = this.authorizedClientService.loadAuthorizedClient(authToken.getAuthorizedClientRegistrationId(), authToken.getName());
   if(authToken.isAuthenticated()){
   
   Map<String,Object> userAttributes = ((DefaultOAuth2User) authToken.getPrincipal()).getAttributes();
   
   String userToken = authClient.getAccessToken().getTokenValue();
   protectedInfo.append("Welcome, " + userAttributes.get("name")+"<br><br>");
   protectedInfo.append("e-mail: " + userAttributes.get("email")+"<br><br>");
   protectedInfo.append("Access Token: " + userToken+"<br><br>");
   }
   else{
   protectedInfo.append("NA");
   }
return protectedInfo;
}

Serai-je en mesure de voir ces informations ? 

Voici une capture d’écran de mes résultats :

Cette capture d'écran montre que l'email n'apparait pas.
Extraire le nom d’utilisateur, l’e-mail et le token d’accès depuis GitHub

Mais pourquoi l’adresse mail n'apparaît pas ? 😭

Lorsque vous procédez à l'extraction d’informations depuis l’objet Principal user , vous avez des informations protégées et non protégées.

Vous pouvez avoir recours à certaines classes, comme principal et authentification, uniquement pour extraire des informations non protégées.

Les classes capables d’extraire les informations protégées disposent de couches d’abstraction, et requièrent davantage de droits. GitHub garde votre adresse mail dans la partie protégée. La seule manière de l’extraire, c’est grâce à l'ID token avec le claim  openid email  dans le scope

Cela nous amène à OpenID Connect !

Utilisez OpenID Connect avec Google

Pourquoi est-ce essentiel d’utiliser OpenID Connect pour une authentification ?

Google est une excellente ressource pour OAuth 2.0, et dispose d’un support performant pour OpenID Connect.

Dans le formulaire, créez un nom pour votre application web client OAuth 2.0.

Afin de travailler sur votre localhost:8080 , mettez en place la configuration suivante :

Restrictions

Identifiants Google OAuth2
Identifiants Google OAuth2

Sauvegardez vos client ID et client secret, afin de saisir les propriétés Google dans votre fichier application.properties, sous les propriétés de GitHub.

spring.security.oauth2.client.registration.google.client-id=<clientid>.apps.googleusercontent.com

spring.security.oauth2.client.registration.google.client-secret=<clientsecret>

La clé pour ajouter OpenID Connect à votre requête originale dans le serveur de connexion est le scope pour  openid  , ainsi que les claims  profil  et  email  .

Heureusement, ce scope et ces claims sont définis par défaut dans notre configuration de sécurité !

Ces derniers vont être récupérés avec le token ID.

À présent, vous devez ajouter la récupération du token ID dans la classe LoginController.java. Cette procédure contribue au fonctionnement du scope  openid  .

Heureusement, Spring Security va nous aider. Dans le prototype de la méthode getUserInfo() on peut injecter un objet OidcUser grâce à l’annotation @AuthenticationPrincipal.

@GetMapping("/*")
	public String getUserInfo(Principal user, @AuthenticationPrincipal OidcUser oidcUser) {
		StringBuffer userInfo = new StringBuffer();
		if (user instanceof UsernamePasswordAuthenticationToken) {
			userInfo.append(getUsernamePasswordLoginInfo(user));
		} else if (user instanceof OAuth2AuthenticationToken) {
			userInfo.append(getOAuth2LoginInfo(user, oidcUser));
		}
		return userInfo.toString();
	}

Puis transmettez cet objet OidcUser à la méthode  getOauth2LoginInfo()  .

Maintenant que vous avez renvoyé votre OidcUser à votre méthode  getOauth2LoginInfo()  , vous allez ajouter du code pour que celle-ci sache quoi en faire.

Commencez avec la classe OidcIdToken.

Créez une variable  idToken  de type OidcIdToken pour contenir votre token ID. Ce dernier sera assigné via la fonction   getIdToken()  de l’OidcUser passé en paramètre.

OidcIdToken idToken = oidcUser.getIdToken();

À présent, ajoutez le Token ID dans protectedInfo . Le Token ID contient les informations des claims (autorisations) que vous avez requises via le scope.

Avec openid  requis par défaut, des claims supplémentaires ont été ajoutées pour profil vérifié et e-mail, afin d’authentifier l’utilisateur

En utilisant le code ci-dessous, vous obtenez un HashMap, avec vos claims que vous pouvez récupérer en ayant recours à la méthode  getClaims()  spécifiée dans la classe  OidcIdToken  .

if(oidcUser != null) {
OidcIdToken idToken = oidcUser.getIdToken();
if(idToken != null) {
protectedInfo.append("idToken value: " + idToken.getTokenValue()+"<br><br>");
protectedInfo.append("Token mapped values <br><br>");
Map<String, Object> claims = idToken.getClaims();
for (String key : claims.keySet()) {
protectedInfo.append("  " + key + ": " + claims.get(key)+"<br>");
}
}
}

J’espère que vous vous êtes amusé ! Il existe une myriade de manières de mettre en place cette page en toute sécurité, en ayant recours à une autorisation et à une authentification basées sur des tokens.

En résumé

Vous avez suivi les étapes suivantes pour créer un formulaire de connexion OAuth 2.0 avec OIDC :

  • Configurer la connexion par défaut OAuth 2.0 dans la chaîne de filtres de sécurité.  

  • Enregistrer votre application web avec GitHub et Google, pour avoir un client ID et un client secret. 

  • Ajouter la configuration GitHub dans le fichier application.properties. 

  • Extraire les informations du principal user détails et du token d’accès depuis GitHub.

  • Configurer OIDC avec GitHub dans le fichier application.properties.

  • Extraire le principal, le token d’accès, et le ID Token depuis Google.

  • Décoder l’ID Token pour extraire les informations du claim.

Vous avez ajouté de super options pour autoriser les utilisateurs à accéder à votre application web, mais comment s’assurer que les utilisateurs lambda n’aient pas accès aux pages qu’ils ne sont pas autorisés à consulter ?

C’est important, il est donc judicieux de mettre en place des tests pour vous assurer que cela fonctionne ! C'est ce que nous allons voir dans le prochain chapitre.

Exemple de certificat de réussite
Exemple de certificat de réussite