View Javadoc
1   /*
2   This file is part of the talbotgui/psl project.
3   Authors: talbotgui.
4   
5   This program is offered under a commercial and under the AGPL license.
6   For commercial licensing, contact me at talbotgui@gmail.com.
7   For AGPL licensing, see below.
8   
9   AGPL licensing:
10  This program is free software: you can redistribute it and/or modify 
11  it under the terms of the GNU Affero General Public License as published by
12  the Free Software Foundation, either version 3 of the License, or
13  (at your option) any later version.
14  
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU Affero General Public License for more details.
19  
20  AGPL license is available in LICENSE.md file and https://www.gnu.org/licenses/#AGPL
21   */
22  package com.github.talbotgui.psl.socle.securite.service;
23  
24  import java.nio.charset.StandardCharsets;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Base64;
28  import java.util.Date;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.regex.Pattern;
33  
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  import org.springframework.beans.factory.annotation.Value;
37  import org.springframework.security.core.userdetails.User;
38  import org.springframework.security.core.userdetails.UserDetails;
39  import org.springframework.stereotype.Service;
40  import org.springframework.util.StringUtils;
41  
42  import com.fasterxml.jackson.core.JsonProcessingException;
43  import com.fasterxml.jackson.core.type.TypeReference;
44  import com.fasterxml.jackson.databind.ObjectMapper;
45  import com.github.talbotgui.psl.socle.commun.oidc.OidcClient;
46  import com.github.talbotgui.psl.socle.commun.oidc.dto.ReponseTokenOIDC;
47  import com.github.talbotgui.psl.socle.commun.securite.JwtService;
48  import com.github.talbotgui.psl.socle.commun.utils.RegexUtils;
49  import com.github.talbotgui.psl.socle.securite.apiclient.dto.ReponseJwtDto;
50  import com.github.talbotgui.psl.socle.securite.apiclient.dto.RequeteCreationTokenOidcDto;
51  import com.github.talbotgui.psl.socle.securite.client.SpClient;
52  import com.github.talbotgui.psl.socle.securite.client.dto.InformationSpCompteDto;
53  import com.github.talbotgui.psl.socle.securite.client.dto.InformationSpUsagerDto;
54  import com.github.talbotgui.psl.socle.securite.exception.SecuriteException;
55  
56  import io.jsonwebtoken.Claims;
57  import io.jsonwebtoken.lang.DateFormats;
58  
59  @Service
60  public class OidcServiceImpl implements OidcService {
61  
62  	private static final Logger LOGGER = LoggerFactory.getLogger(OidcServiceImpl.class);
63  
64  	/** Pattern vérifiant les caractères autorisés dans un mot de passe. */
65  	private static final String PATTERN_MOT_DE_PASSE_GLOBAL = "^[a-zA-Z0-9 \\.!#$%&'\"*+\\/=?^_`{|}~\\-@]*$";
66  
67  	/** Pattern vérifiant la présence d'au moins un caractère de chaque type */
68  	// /!\ à garder cohérent avec PATTERN_MOT_DE_PASSE_GLOBAL
69  	private static final List<String> PATTERNS_CRITERES_MOT_DE_PASSE = Arrays.asList("^.*[a-z].*$", "^.*[A-Z].*$", "^.*[0-9].*$",
70  			"^.*[ \\.!#$%&'\"*+\\/=?^_`{|}~\\-@].*$");
71  
72  	/**
73  	 * Regex de validation des nom d'utilisateur. (https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address)
74  	 */
75  	// Cette regex est dupliquée ici car son besoin est très précis et, si une modification est à faire, il ne faut peut-être pas modifié RegexUtils.
76  	private static final String REGEX_VALIDATION_EMAIL = RegexUtils.EMAIL;
77  
78  	/** Paramètre OIDC. */
79  	private final String clientId;
80  
81  	/** Paramètre OIDC. */
82  	private final String clientSecret;
83  
84  	private final JwtService jwtService;
85  	private final OidcClient oidcClient;
86  	private final SpClient spClient;
87  
88  	/** Constructeur pour l'injection des dépendances (cf. chapitre §3.22.5). */
89  	public OidcServiceImpl(@Value("${oidc.clientId}") String clientId, @Value("${oidc.clientSecret}") String clientSecret, JwtService jwtService,
90  			OidcClient oidcClient, SpClient spClient) {
91  		super();
92  		this.clientId = clientId;
93  		this.clientSecret = clientSecret;
94  		this.jwtService = jwtService;
95  		this.oidcClient = oidcClient;
96  		this.spClient = spClient;
97  
98  		// Message d'avertissement concernant les logs TRACE de cette classe.
99  		if (LOGGER.isTraceEnabled()) {
100 			LOGGER.warn("Le niveau de log TRACE pour cette classe ne doit EN AUCUN CAS étre actif en production !! (données sensibles/personnelles)");
101 		}
102 	}
103 
104 	/**
105 	 * Chargement des données de l'usager et du compte (2 requêtes) pour vérifier que le token est bon.
106 	 *
107 	 * @param accessTokenSP AccessToken SP.
108 	 * @return Les données de l'usager et du compte dans un même DTO.
109 	 */
110 	private InformationSpUsagerDto chargerDonneesUsagerEtCompte(String accessTokenSP) {
111 
112 		// Recherche des données de l'usager connecté
113 		InformationSpUsagerDto donneesUsager;
114 		try {
115 			donneesUsager = this.spClient.chargerDonneesPersonnelles(this.clientId, this.clientSecret, accessTokenSP);
116 		} catch (Exception e) {
117 			// Ce log DEBUG pour disposer d'un moyen d'obtenir la stacktraced
118 			LOGGER.debug("Erreur à la recherche des données de l'usager connecté", e);
119 			LOGGER.warn("Erreur de chargement des données personnelles : {}", e.getMessage());
120 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "cduec1");
121 		}
122 
123 		// Si les données retournées sont insuffisantes
124 		if ((donneesUsager == null) || !StringUtils.hasLength(donneesUsager.getEmail())) {
125 			LOGGER.warn("Erreur de chargement des données personnelles : les données sont incomplètes");
126 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "cduec2");
127 		}
128 
129 		try {
130 			// Recherche des données du compte connecté
131 			InformationSpCompteDto donneesCompte = this.spClient.chargerDonneesCompte(this.clientId, this.clientSecret, accessTokenSP);
132 
133 			// Copie des données obtenues dans les données de l'usager
134 			donneesUsager.setAccountType(donneesCompte.getAccountType());
135 			donneesUsager.setFranceConnect(donneesCompte.getFranceConnect());
136 			donneesUsager.setEmailTechnique(donneesCompte.getEmail());
137 			donneesUsager.setUuidSp(donneesCompte.getSub());
138 
139 		} catch (Exception e) {
140 			// Ce log DEBUG pour disposer d'un moyen d'obtenir la stacktrace
141 			LOGGER.debug("Erreur à la recherche des données du compte connecté", e);
142 			LOGGER.warn("Erreur de chargement des données du compte : {}", e.getMessage());
143 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "cduec3");
144 		}
145 		return donneesUsager;
146 	}
147 
148 	@Override
149 	public InformationSpUsagerDto chargerDonneesUsagerEtCompteAvecUnTokenPsl(String tokenPSL) {
150 		// Validation et lecture du token
151 		Claims claimsPsl = this.jwtService.validerToken(tokenPSL);
152 		if (claimsPsl == null) {
153 			LOGGER.warn("Token PSL invalide");
154 			throw new SecuriteException(SecuriteException.DONNEES_USAGER_INDISPONIBLES, "1");
155 		}
156 
157 		// Lecture des claims
158 		String emailDansSubject = claimsPsl.getSubject();
159 		String accessTokenOidcExistant = claimsPsl.get(JwtService.CLEF_CLAIMS_ACCESS_TOKEN_OIDC, String.class);
160 
161 		// Si le token PSL est anonyme, on renvoi une donnée fixe
162 		if (JwtService.EMAIL_UTILISATEUR_ANONYMOUS.equals(emailDansSubject)) {
163 			return new InformationSpUsagerDto(JwtService.EMAIL_UTILISATEUR_ANONYMOUS, "", "");
164 		}
165 
166 		// Sinon, chargement des données depuis le fournisseur OIDC
167 		else {
168 			return this.chargerDonneesUsagerEtCompte(accessTokenOidcExistant);
169 		}
170 	}
171 
172 	/**
173 	 * Méthode créant un token auprès du fournisseur OIDC à partir des informations échangées entre l'application FRONT et la page de connexion du
174 	 * fournisseur OIDC.
175 	 *
176 	 * Ce token est ensuite encapsulé dans un token PSL.
177 	 *
178 	 * @param requete Requête TOKEN OIDC
179 	 * @return un token PSL
180 	 */
181 	private ReponseTokenOIDC creerLeToken(RequeteCreationTokenOidcDto requete) {
182 
183 		// Appel au fournisseur OIDC
184 		ReponseTokenOIDC reponseOidc;
185 		try {
186 			reponseOidc = this.oidcClient.creerAccessToken(requete.grantType(), requete.code(), requete.redirectUri(), requete.codeVerifier(),
187 					this.clientId, this.clientSecret, requete.refreshToken());
188 		} catch (Exception e) {
189 			// Ce log DEBUG pour disposer d'un moyen d'obtenir la stacktrace
190 			LOGGER.warn("Erreur de création du token OIDC", e);
191 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "c1");
192 		}
193 
194 		// Validation de la réponse obtenue
195 		if ((reponseOidc == null) || !StringUtils.hasLength(reponseOidc.accessToken()) || !StringUtils.hasLength(reponseOidc.refreshToken())) {
196 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "c2");
197 		}
198 
199 		// Chargement des données de l'usager et du compte (2 requêtes) pour vérifier que le token est bon
200 		InformationSpUsagerDto donneesUsager = this.chargerDonneesUsagerEtCompte(reponseOidc.accessToken());
201 
202 		// Création du token PSL à partir du token OIDC
203 		return this.creerTokenPslApartirDuTokenOIDC(reponseOidc, donneesUsager);
204 	}
205 
206 	@Override
207 	public ReponseTokenOIDC creerOuRaffrachirLeToken(RequeteCreationTokenOidcDto requete) {
208 
209 		// Pour une création de token
210 		if (!"refresh_token".equals(requete.grantType())) {
211 			return this.creerLeToken(requete);
212 		} else {
213 			return this.raffraichirLeToken(requete);
214 		}
215 	}
216 
217 	/**
218 	 * Création d'un token PSL à partir des informations fournies.
219 	 *
220 	 * @param userDetails Détails de l'utilisateur connecté.
221 	 * @return Token PSL.
222 	 */
223 	private ReponseJwtDto creerTokenPsl(UserDetails userDetails) {
224 
225 		// Création d'un token
226 		String token = this.jwtService.genererNouveauToken(userDetails);
227 
228 		// Renvoi du JWT
229 		return new ReponseJwtDto(token);
230 	}
231 
232 	/**
233 	 * Création d'un token PSL à partir des données obtenues du fournisseur OIDC.
234 	 *
235 	 * @param reponseOidc   Réponse de création/refresh de token OIDC.
236 	 * @param donneesUsager Données de l'usager SP.
237 	 * @return Un token PSL complet contenant les tokens OIDC.
238 	 */
239 	private ReponseTokenOIDC creerTokenPslApartirDuTokenOIDC(ReponseTokenOIDC reponseOidc, InformationSpUsagerDto donneesUsager) {
240 		// Calcul de la date de péremption de l'accessToken OIDC
241 		Date dateExpirationRefreshOidcExistant = new Date(new Date().getTime() + (Integer.parseInt(reponseOidc.expiresIn()) * 1000));
242 		Date dateExpirationAccessOidcExistant = new Date(new Date().getTime() + (Integer.parseInt(reponseOidc.refreshExpiresIn()) * 1000));
243 
244 		// Extraction du mail venant de la partie publique de l'accessToken
245 		String emailSP = this.extraireEmailDeLaPartiePubliqueDuToken(reponseOidc.accessToken());
246 
247 		// Création de la liste des données à mettre en clair dans le token
248 		Map<String, Object> claims = new HashMap<>(Map.of(//
249 				Claims.SUBJECT, emailSP, //
250 				JwtService.CLEF_CLAIMS_FC, donneesUsager.getFranceConnect(), //
251 				JwtService.CLEF_CLAIMS_ACCOUNT_TYPE, donneesUsager.getAccountType()//
252 		));
253 
254 		// Création de la liste des données à chiffrer dans le token
255 		Map<String, Object> donneesAchiffrer = Map.of(//
256 				JwtService.CLEF_CLAIMS_UUID_SP, donneesUsager.getUuidSp(), //
257 				JwtService.CLEF_CLAIMS_ACCESS_TOKEN_OIDC, reponseOidc.accessToken(), //
258 				JwtService.CLEF_CLAIMS_REFRESH_TOKEN_OIDC, reponseOidc.refreshToken(), //
259 				JwtService.CLEF_CLAIMS_EXPIRATION_ACCESSTOKEN_OIDC, DateFormats.formatIso8601(dateExpirationAccessOidcExistant), //
260 				JwtService.CLEF_CLAIMS_EXPIRATION_REFRESHTOKEN_OIDC, DateFormats.formatIso8601(dateExpirationRefreshOidcExistant)//
261 		);
262 
263 		// Recréation d'un nouveau token PSL
264 		UserDetails utilisateur = new User(emailSP, "", new ArrayList<>());
265 		String nouveauToken = this.jwtService.genererNouveauToken(utilisateur, claims, donneesAchiffrer);
266 
267 		// Renvoi du token PSL contenant :
268 		// - les réelles dates d'expiration (access et refresh)
269 		// - le token custom PSL dans l'accessToken (pour être utilisé à chaque appel d'API).
270 		// - le token custom PSL dans le refreshToken (pour être utilisé au raffraichissement du token PSL).
271 		// - l'ID token n'est pas renvoyé pour ne pas divulguer d'informations.
272 		// - scope et state sont déjà connus de l'application FRONT.
273 		return new ReponseTokenOIDC(nouveauToken, reponseOidc.expiresIn(), reponseOidc.refreshExpiresIn(), nouveauToken, "PSL", "",
274 				reponseOidc.sessionState(), reponseOidc.scope());
275 	}
276 
277 	/**
278 	 * Méthode extrayant le contenu de l'attribut 'email' de la partie publique du token.
279 	 *
280 	 * @param token Token à lire.
281 	 * @return Email trouvé (null si rien)
282 	 */
283 	private String extraireEmailDeLaPartiePubliqueDuToken(String token) {
284 
285 		// Découpage du token
286 		String[] tokenDecoupe = token.split("\\.");
287 		if (tokenDecoupe.length != 3) {
288 			LOGGER.warn("Token mal formaté");
289 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "e1");
290 		}
291 
292 		// Décodage de la partie publique
293 		String partiePubliqueTokenDecode = new String(Base64.getDecoder().decode(tokenDecoupe[1].getBytes(StandardCharsets.UTF_8)),
294 				StandardCharsets.UTF_8);
295 
296 		// Parse du Json
297 		Map<String, Object> resultat;
298 		try {
299 			resultat = new ObjectMapper().readValue(partiePubliqueTokenDecode, new TypeReference<Map<String, Object>>() {
300 			});
301 		} catch (JsonProcessingException e) {
302 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "e2");
303 		}
304 
305 		// Vérification du contenu des données
306 		Object email = resultat.get("email");
307 		if (email == null) {
308 			LOGGER.warn("Pas d'email dans le JSON de la partie publique du token");
309 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "e3");
310 		}
311 
312 		// Renvoi de l'email
313 		return resultat.get("email").toString();
314 	}
315 
316 	/**
317 	 * Méthode créant un token auprès du fournisseur OIDC à partir des informations échangées entre l'application FRONT et la page de connexion du
318 	 * fournisseur OIDC.
319 	 *
320 	 * Ce token est ensuite encapsulé dans un token PSL.
321 	 *
322 	 * @param requete Requête TOKEN OIDC
323 	 * @return un token PSL
324 	 */
325 	private ReponseTokenOIDC raffraichirLeToken(RequeteCreationTokenOidcDto requete) {
326 		String tokenPSL = requete.refreshToken();
327 
328 		// Validation et lecture du token
329 		Claims claimsPsl = this.jwtService.validerToken(tokenPSL);
330 		if (claimsPsl == null) {
331 			LOGGER.warn("Token PSL invalide");
332 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "r1");
333 		}
334 
335 		// Lecture des claims
336 		String emailUtilisateur = claimsPsl.getSubject();
337 		String refreshTokenOidcExistant = claimsPsl.get(JwtService.CLEF_CLAIMS_REFRESH_TOKEN_OIDC, String.class);
338 		String motDePasseEventuel = claimsPsl.get(JwtService.CLEF_CLAIMS_MOT_DE_PASSE, String.class);
339 		Date dateExpirationAccessOidcExistant = claimsPsl.get(JwtService.CLEF_CLAIMS_EXPIRATION_ACCESSTOKEN_OIDC, Date.class);
340 		Date dateExpirationRefreshOidcExistant = claimsPsl.get(JwtService.CLEF_CLAIMS_EXPIRATION_REFRESHTOKEN_OIDC, Date.class);
341 
342 		// Si le token est anonyme, il est raffraichit
343 		if (StringUtils.hasLength(emailUtilisateur) && JwtService.EMAIL_UTILISATEUR_ANONYMOUS.equals(emailUtilisateur)) {
344 			ReponseJwtDto tokenAnonyme = this.sauthentifierEnAnonyme();
345 			return new ReponseTokenOIDC(tokenAnonyme.token(), null, null, tokenAnonyme.token(), null, null, null, null);
346 		}
347 
348 		// Si le token est issu d'une authentification par nom d'utilisateur et mot de passe
349 		if (StringUtils.hasLength(motDePasseEventuel)) {
350 			ReponseJwtDto tokenAnonyme = this.sauthentifierAvecUnNomDutilisateurEtUnMotDePasse(emailUtilisateur, motDePasseEventuel);
351 			return new ReponseTokenOIDC(tokenAnonyme.token(), null, null, tokenAnonyme.token(), null, null, null, null);
352 		}
353 
354 		// Validation des données du token
355 		if (!StringUtils.hasLength(refreshTokenOidcExistant) || (dateExpirationAccessOidcExistant == null)
356 				|| (dateExpirationRefreshOidcExistant == null)) {
357 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "r2");
358 		}
359 		if (dateExpirationRefreshOidcExistant.before(new Date())) {
360 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "r3");
361 		}
362 
363 		// Raffraichissement du token
364 		ReponseTokenOIDC reponseOidc;
365 		try {
366 			reponseOidc = this.oidcClient.creerAccessToken(requete.grantType(), requete.code(), requete.redirectUri(), requete.codeVerifier(),
367 					this.clientId, this.clientSecret, refreshTokenOidcExistant);
368 			LOGGER.trace("RefreshToken réalisé : {}", reponseOidc);
369 		} catch (Exception e) {
370 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, e, "r4");
371 		}
372 
373 		if ((reponseOidc == null) || !StringUtils.hasLength(reponseOidc.accessToken()) || !StringUtils.hasLength(reponseOidc.refreshToken())) {
374 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "r5");
375 		}
376 
377 		// Extraction de quelques données des tokens OIDC obtenus
378 		String emailAccessTokenSP = this.extraireEmailDeLaPartiePubliqueDuToken(reponseOidc.accessToken());
379 		String emailIdTokenSP = this.extraireEmailDeLaPartiePubliqueDuToken(reponseOidc.idToken());
380 
381 		// Validation cohérence des données dans le token PSL vis-à-vis des données dans les tokens OIDC
382 		if ((emailUtilisateur == null) || !emailAccessTokenSP.equals(emailUtilisateur) || !emailIdTokenSP.equals(emailUtilisateur)) {
383 			throw new SecuriteException(SecuriteException.ACCESSTOKEN_NON_ENREGISTRABLE, "r6");
384 		}
385 
386 		// Chargement des données de l'usager et du compte (2 requêtes) pour vérifier que le token est bon
387 		InformationSpUsagerDto donneesUsager = this.chargerDonneesUsagerEtCompte(reponseOidc.accessToken());
388 
389 		// Création du token PSL à partir du token OIDC
390 		return this.creerTokenPslApartirDuTokenOIDC(reponseOidc, donneesUsager);
391 	}
392 
393 	@Override
394 	public ReponseJwtDto sauthentifierAvecUnNomDutilisateurEtUnMotDePasse(String nomUtilisateur, String motDePasse) {
395 
396 		// Validation de la présence des données
397 		if (!StringUtils.hasLength(nomUtilisateur)) {
398 			throw new SecuriteException(SecuriteException.DONNEES_AUTHENTIFICATION_INVALIDE, "1");
399 		}
400 		if (!StringUtils.hasLength(motDePasse)) {
401 			throw new SecuriteException(SecuriteException.DONNEES_AUTHENTIFICATION_INVALIDE, "3");
402 		}
403 
404 		// Trim systématique du nom d'utilisateur pour éviter les invalidations à cause d'un simple espace
405 		nomUtilisateur = nomUtilisateur.trim();
406 
407 		// Validation des contenus
408 		if (!Pattern.matches(REGEX_VALIDATION_EMAIL, nomUtilisateur)) {
409 			throw new SecuriteException(SecuriteException.DONNEES_AUTHENTIFICATION_INVALIDE, "2");
410 		}
411 		this.validerMotDePasse(motDePasse);
412 
413 		// Uppercase systématique du nom d'utilisateur pour être insensible à la CASSE à l'usage du login
414 		nomUtilisateur = nomUtilisateur.trim().toUpperCase();
415 
416 		// Création et renvoi du token
417 		return this.creerTokenPsl(new User(nomUtilisateur, motDePasse, new ArrayList<>()));
418 	}
419 
420 	@Override
421 	public ReponseJwtDto sauthentifierEnAnonyme() {
422 		return this.creerTokenPsl(JwtService.USER_ANONYME);
423 	}
424 
425 	/**
426 	 * Validation du mot de passe pour avoir au moins 8 caractères de long, sans caractères tordus (cf. PATTERN_MOT_DE_PASSE) et 3 critères parmi :
427 	 * <ul>
428 	 * <li>un chiffre</li>
429 	 * <li>un caractère spécial</li>
430 	 * <li>une majuscule</li>
431 	 * <li>une minuscule</li>
432 	 * </ul>
433 	 *
434 	 * @param motDePasse
435 	 */
436 	private void validerMotDePasse(String motDePasse) {
437 		// Contrôle de longueur
438 		if (motDePasse.length() < 8) {
439 			throw new SecuriteException(SecuriteException.DONNEES_AUTHENTIFICATION_INVALIDE, "4-1");
440 		}
441 
442 		// Contrôle des caractères interdits
443 		if (!Pattern.matches(PATTERN_MOT_DE_PASSE_GLOBAL, motDePasse)) {
444 			throw new SecuriteException(SecuriteException.DONNEES_AUTHENTIFICATION_INVALIDE, "4-2");
445 		}
446 
447 		// Contrôle des critères
448 		long nbCriteresRespectes = PATTERNS_CRITERES_MOT_DE_PASSE.stream().filter(p -> Pattern.matches(p, motDePasse)).count();
449 		if (nbCriteresRespectes < 3) {
450 			throw new SecuriteException(SecuriteException.DONNEES_AUTHENTIFICATION_INVALIDE, "4-3");
451 		}
452 	}
453 }