On va commencer par l’exemple le plus simple avec le moins de code possible pour découvrir le framework de sécurité JAAS.
import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; public class Main { public static void main(String[] args) { // Configuration du contexte de login System.setProperty("java.security.auth.login.config", "jaas.config"); // Déclaration et initialisation du contexte de login LoginContext loginContext = null; try { loginContext = new LoginContext("JaasSample"); } catch (LoginException e) { e.printStackTrace(); } // Tentative de login try { loginContext.login(); System.out.println("Authentification réussie !"); } catch (LoginException e) { System.out.println("Authentification échouée."); e.printStackTrace(); } } }
// fichier jaas.config JaasSample { com.example.SampleLoginModule required; };
import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; public class SampleLoginModule implements LoginModule { private Subject subject; private CallbackHandler callbackHandler; private String username; private char[] password; @Override public void initialize(Subject subject, CallbackHandler callbackHandler, java.util.Map<String, ?> sharedState, java.util.Map<String, ?> options) { this.subject = subject; this.callbackHandler = callbackHandler; } @Override public boolean login() throws LoginException { // on crée un tableau de 2 objets Callback[] callbacks = new Callback[2]; // on y stocke 2 autres objets qui servent à demander au user son username et son password en mode prompt callbacks[0] = new NameCallback("Username: "); // permet la saisie du username callbacks[1] = new PasswordCallback("Password: ", false); // try { /* appelle la méthode handle de l'objet callbackHandler pour demander l'entrée de l'utilisateur. Si l'objet callbackHandler ne prend pas en charge l'un des types de Callback, une UnsupportedCallbackException est levée. Si une exception de type java.io.IOException est levée, cela signifie qu'il y a eu un problème lors de la communication avec l'utilisateur. */ callbackHandler.handle(callbacks); } catch (UnsupportedCallbackException e) { throw new LoginException(e.getMessage()); } catch (java.io.IOException e) { throw new LoginException(e.getMessage()); } username = ((NameCallback) callbacks[0]).getName(); // on récupère le username saisi char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword(); // on récupre le mot de passe saisi if (tmpPassword == null) { tmpPassword = new char[0]; // on affecte un tableau vide de caractères } password = new char[tmpPassword.length]; // sinon on crée un tableau de la même longueur que tmpPassword. // la méthode System.arraycopy() permet de copier les éléments de tmpPassword dans le nouveau tableau password. System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length); // pour assurer la sécurité de l'application, on utilise la méthode clearPassord() // pour effacer le mpt de passe ((PasswordCallback) callbacks[1]).clearPassword(); // Vérifiez ici si l'utilisateur est valide en utilisant le nom d'utilisateur et le mot de passe boolean loginSuccessful = checkUser(username, password); return loginSuccessful; } private boolean checkUser(String username, char[] password) { // Cette méthode vérifie si le nom d'utilisateur et le mot de passe sont valides. // Pour simplifier, on va dire que seul l'utilisateur "admin" avec le mot de passe "password" est valide. return username.equals("admin") && Arrays.equals(password, "password".toCharArray()); } /** * La méthode commit est appelée par le LoginContext lorsque la procédure de connexion est terminée * et que tous les LoginModule ont été invoqués avec succès. Cette méthode est utilisée pour finaliser * la procédure de connexion en ajoutant les informations d'identification au sujet si la vérification a réussi. */ @Override public boolean commit() throws LoginException { /* Si la vérification a réussi, ajoutez les informations d'identification au sujet représente l'utilisateur qui tente de se connecter, ainsi que toutes les informations d'identification et autorisations associées à cet utilisateur. Les principes sont des informations d'identification associées au sujet, comme le nom d'utilisateur et les rôles de l'utilisateur. */ if (checkUser(username, password)) { subject.getPrincipals().add(new UserPrincipal(username)); return true; } else { return false; } } @Override public boolean abort() throws LoginException { return true; } @Override public boolean logout() throws LoginException { subject.getPrincipals().remove(new UserPrincipal(username)); return true; } }
Cet exemple montre comment utiliser JAAS pour authentifier un utilisateur en demandant :
Que fait la méthode initialize() ?
Elle initialise l’objet LoginModule :
sharedState
options
Il est important de noter que la méthode initialize() est appelée par le LoginContext et n’est pas censée être appelée directement par le code de l’application.
Voici un exemple de programme Spring Boot utilisant JAAS (Java Authentication and Authorization Service) pour authentifier les utilisateurs :
D’abord, vous devez ajouter la dépendance Spring Security et configurer JAAS dans votre fichier application.properties :
application.properties
spring.security.jaas.login-module=SampleLoginModule spring.security.jaas.config=file:/etc/jaas.config
Ensuite, vous pouvez créer une classe de configuration de sécurité Spring qui utilise JAAS pour authentifier les utilisateurs :
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.jaas.JaasAuthenticationProvider; @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // Configurez les règles de sécurité ici http.authorizeRequests().anyRequest().authenticated().and().formLogin(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // Ajoutez un fournisseur d'authentification JAAS auth.authenticationProvider(new JaasAuthenticationProvider()); } }
Enfin, vous pouvez créer votre module de login JAAS qui s’occupe de vérifier les informations d’identification de l’utilisateur :
module
import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import java.util.Map; public class SampleLoginModule implements LoginModule { @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { // Initialisez le module de login ici } @Override public boolean login() throws LoginException { // Vérifiez les informations d'identification de l'utilisateur ici return true; } @Override public boolean commit() throws LoginException { // Ajoutez les informations d'identification du sujet ici return true; } @Override public boolean abort() throws LoginException { // Annulez l'authentification si nécessaire ici return true; } @Override public boolean logout() throws LoginException { // Retirez les informations d'identification du sujet ici return true; } }
Dans cet exemple, le module de login SampleLoginModule implémente l’interface LoginModule de JAAS et s’occupe de vérifier les informations d’identification de l’utilisateur.
La méthode login() est appelée pour effectuer la tentative de login et doit renvoyer true si l’authentification réussit et false si elle échoue.
La méthode commit() est appelée si l’authentification réussit et doit ajouter les informations d’identification du sujet.
Les méthodes abort() et logout() sont appelées respectivement en cas d’échec de l’authentification ou de déconnexion de l’utilisateur.
Vous pouvez personnaliser le module de login en implémentant les méthodes de l’interface LoginModule de manière appropriée et en configurant
Pour utiliser le module de login SampleLoginModule dans votre application Spring Boot, vous devez le configurer dans le fichier jaas.config. Voici un exemple de configuration de SampleLoginModule :
SampleLoginModule { com.example.SampleLoginModule required; };
Cette configuration indique à JAAS d’utiliser le module de login SampleLoginModule de votre application. Vous devez également définir le chemin vers le fichier de configuration de JAAS dans le fichier application.properties de votre application :
Une fois que vous avez configuré JAAS et votre module de login, vous pouvez utiliser les annotations de sécurité de Spring pour protéger les ressources de votre application et exiger une authentification pour y accéder.
Par exemple, vous pouvez ajouter l’annotation @Secured(“ROLE_USER”) à une méthode de contrôleur pour exiger qu’un utilisateur authentifié avec le rôle USER puisse accéder à la ressource protégée.
Un CallbackHandler est une interface de Java qui permet à un module de sécurité d’obtenir des informations d’identification de l’utilisateur. Les Callbacks sont des objets qui demandent des informations à l’utilisateur, comme son nom d’utilisateur et son mot de passe. Un CallbackHandler prend en charge ces objets Callback et fournit les informations demandées à l’application.
Le CallbackHandler est utilisé par le module de login pour obtenir les informations d’identification de l’utilisateur. Par exemple, dans l’exemple de code précédent, le SampleLoginModule utilise un CallbackHandler pour demander le nom d’utilisateur et le mot de passe à l’utilisateur. L’application définit le CallbackHandler à utiliser et le module de login l’appelle pour obtenir les informations d’identification de l’utilisateur.
Voici un exemple de ce à quoi pourrait ressembler une implémentation d’un CallbackHandler :
import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class MyCallbackHandler implements CallbackHandler { @Override public void handle(Callback[] callbacks) throws UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof NameCallback) { NameCallback nameCallback = (NameCallback) callback; System.out.print(nameCallback.getPrompt()); nameCallback.setName(readLine()); } else if (callback instanceof PasswordCallback) { PasswordCallback passwordCallback = (PasswordCallback) callback; System.out.print(passwordCallback.getPrompt()); passwordCallback.setPassword(readLine().toCharArray()); } else { throw new UnsupportedCallbackException(callback); } } } private String readLine() { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); try { return reader.readLine(); } catch (IOException e) { return ""; } } }
Cet exemple d’implémentation d’un CallbackHandler gère les Callbacks de type NameCallback et PasswordCallback. Pour chaque Callback, il affiche le prompt demandant l’information à l’utilisateur et lit la réponse de l’utilisateur à partir de l’entrée standard. Les informations lues sont ensuite fournies au Callback en utilisant les méthodes setName et setPassword. Si le Callback n’est pas reconnu, une exception UnsupportedCallbackException est levée.
Le CallbackHandler est utilisé par le module de login pour obtenir les informations d’identification de
Le CallbackHandler est utilisé par le module de login pour obtenir les informations d’identification de l’utilisateur. Dans l’exemple de code précédent, le module de login SampleLoginModule utilise un CallbackHandler pour demander le nom d’utilisateur et le mot de passe à l’utilisateur. L’application définit le CallbackHandler à utiliser et le module de login l’appelle pour obtenir les informations d’identification de l’utilisateur.
Le code new NameCallback(“Username: “) crée un nouvel objet NameCallback, qui est utilisé pour demander à l’utilisateur de saisir un nom d’utilisateur.
La chaîne de caractères “Username: “ est utilisée comme prompt, c’est-à-dire comme message à afficher à l’utilisateur lors de la demande d’entrée du nom d’utilisateur.
Voici comment cela pourrait être utilisé dans le programme :
System.out.print(new NameCallback("Username: ").getPrompt()); Scanner scanner = new Scanner(System.in); String username = scanner.nextLine();
Le code ci-dessus affichera “Username: “ à l’écran et attendra que l’utilisateur saisisse un nom d’utilisateur.