From d8d32966211d6c68213cbb44d818e29916e1e205 Mon Sep 17 00:00:00 2001 From: Ramzi Benmansour Date: Fri, 5 May 2023 13:03:04 +0200 Subject: [PATCH 1/2] hashed pwd sent to the DB instead of real one and all pseudo are unique --- src/main/java/Forms/LoginFormChecker.java | 18 ++- src/main/java/Forms/SigninFormChecker.java | 20 ++- .../helpers/PasswordAuthentification.java | 139 ++++++++++++++++++ 3 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 src/main/java/helpers/PasswordAuthentification.java diff --git a/src/main/java/Forms/LoginFormChecker.java b/src/main/java/Forms/LoginFormChecker.java index b39833f..990d044 100644 --- a/src/main/java/Forms/LoginFormChecker.java +++ b/src/main/java/Forms/LoginFormChecker.java @@ -9,9 +9,9 @@ package Forms; * * @author stag */ -import static com.sun.tools.javac.tree.TreeInfo.name; import fr.ldnr.beans.User; import fr.ldnr.dao.DaoFactory; +import helpers.PasswordAuthentification; import javax.servlet.http.HttpServletRequest; public class LoginFormChecker extends FormChecker { @@ -30,7 +30,6 @@ public class LoginFormChecker extends FormChecker { bean = new User(23, null, pseudo, pwd); - if (pseudo == null || pseudo.trim().length() < 3) { errors.put(PSEUDO_FIELD, "Doit faire au moins 3 caractères"); } @@ -42,11 +41,18 @@ public class LoginFormChecker extends FormChecker { if (errors.isEmpty()) { User user = DaoFactory.getUserDao().getByName(pseudo); // Vérifications de l'existence de l'utilisateur - if (user == null || !user.getPassword().equals(pwd)) { + PasswordAuthentification pa = new PasswordAuthentification(); + try { + // Attention, la méthode authenticate lance des exceptions silencieuses... + if (user == null || !pa.authenticate(pwd.toCharArray(), user.getPassword())) { + errors.put(PSEUDO_FIELD, "Utilisateur ou mot de passe erroné"); + } else { + // L'utilisateur est le bon + bean = user; + } + } catch (IllegalArgumentException ex) { errors.put(PSEUDO_FIELD, "Utilisateur ou mot de passe erroné"); - } else { - // L'utilisateur est le bon - bean = user; + } } diff --git a/src/main/java/Forms/SigninFormChecker.java b/src/main/java/Forms/SigninFormChecker.java index 26efc16..29f0da4 100644 --- a/src/main/java/Forms/SigninFormChecker.java +++ b/src/main/java/Forms/SigninFormChecker.java @@ -11,6 +11,7 @@ package Forms; */ import fr.ldnr.beans.User; import fr.ldnr.dao.DaoFactory; +import helpers.PasswordAuthentification; import javax.servlet.http.HttpServletRequest; public class SigninFormChecker extends FormChecker { @@ -40,10 +41,13 @@ public class SigninFormChecker extends FormChecker { errors.put(EMAIL_FIELD, "L'adresse email n'est pas valide."); } - if (password == null || password.trim().isEmpty()) { - errors.put(PWD_FIELD, "Le mot de passe est obligatoire."); - } else if (password.length() < 6) { - errors.put(PWD_FIELD, "Le mot de passe doit contenir au moins 6 caractères."); + if (password == null || password.length() < 6) { + password = ""; // nécessaire pour simplifier la tentative de hashage + errors.put(PWD_FIELD, "Doit contenir au moins 6 caractères"); + } else { + if (confirmPassword == null || !password.equals(confirmPassword)) { + errors.put(CONFIRM_PWD_FIELD, "Doit être identique au mot de passe"); + } } if (confirmPassword == null || confirmPassword.trim().isEmpty()) { @@ -58,9 +62,13 @@ public class SigninFormChecker extends FormChecker { errors.put(PSEUDO_FIELD, "Le pseudo doit contenir au moins 3 caractères."); } - if (errors.isEmpty()) { // Si le formulaire est correct - // Créer l'utilisateur en DB + if (errors.isEmpty()) { + PasswordAuthentification pa = new PasswordAuthentification(); + bean.setPassword(pa.hash(password.toCharArray())); DaoFactory.getUserDao().insert(bean); + if(bean.getId() == null){ + errors.put(PSEUDO_FIELD, "Ce pseudo est déja utilisé !!!"); + } } return errors.isEmpty(); diff --git a/src/main/java/helpers/PasswordAuthentification.java b/src/main/java/helpers/PasswordAuthentification.java new file mode 100644 index 0000000..169731f --- /dev/null +++ b/src/main/java/helpers/PasswordAuthentification.java @@ -0,0 +1,139 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package helpers; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Arrays; +import java.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +/** + * + * @author stag + */ +public final class PasswordAuthentification { + + private static final String INIT_ID = "51"; + + /** + * Each token produced by this class uses this identifier as a prefix. + */ + public static final String ID = "$" + INIT_ID + "$"; + + /** + * The minimum recommended cost, used by default + */ + public static final int DEFAULT_COST = 16; + private static final String ALGORITHM = "PBKDF2WithHmacSHA512"; + private static final int SIZE = 128; + private static final Pattern LAYOUT = Pattern.compile("\\$" + INIT_ID + "\\$(\\d\\d?)\\$(.{43})"); + private final SecureRandom random; + private final int cost; + + /** + * Create a password manager with a default cost + */ + public PasswordAuthentification() { + this(DEFAULT_COST); + } + + /** + * Create a password manager with a specified cost + * + * @param cost the exponential computational cost of hashing a password, 0 + * to 30 + */ + public PasswordAuthentification(int cost) { + iterations(cost); + /* Validate cost */ + this.cost = cost; + this.random = new SecureRandom(); + } + + private static int iterations(int cost) { + if ((cost < 0) || (cost > 30)) { + throw new IllegalArgumentException("cost: " + cost); + } + return 1 << cost; // Décalage du bit à gauche : 0001 << 3 revient à 1000 + } + + /** + * Hash a password for storage. + * + * @param password The password to hash + * @return a secure authentication token to be stored for later + * authentication + */ + public String hash(char[] password) { + byte[] salt = new byte[SIZE / 8]; + random.nextBytes(salt); + byte[] dk = pbkdf2(password, salt, 1 << cost); + byte[] hash = new byte[salt.length + dk.length]; + System.arraycopy(salt, 0, hash, 0, salt.length); + System.arraycopy(dk, 0, hash, salt.length, dk.length); + Base64.Encoder enc = Base64.getUrlEncoder().withoutPadding(); + return ID + cost + '$' + enc.encodeToString(hash); + } + + /** + * Authenticate with a password and a stored password token. + * + * @param password The password to compare + * @param token The token to compare the password to + * @return true if the password and token match + */ + public boolean authenticate(char[] password, String token) { + Matcher m = LAYOUT.matcher(token); + if (!m.matches()) { + throw new IllegalArgumentException("Invalid token format"); + } + int iterations = iterations(Integer.parseInt(m.group(1))); + byte[] hash = Base64.getUrlDecoder().decode(m.group(2)); + byte[] salt = Arrays.copyOfRange(hash, 0, SIZE / 8); + byte[] check = pbkdf2(password, salt, iterations); + int zero = 0; + for (int idx = 0; idx < check.length; ++idx) { + zero |= hash[salt.length + idx] ^ check[idx]; + } + return zero == 0; + } + + private static byte[] pbkdf2(char[] password, byte[] salt, int iterations) { + KeySpec spec = new PBEKeySpec(password, salt, iterations, SIZE); + try { + SecretKeyFactory f = SecretKeyFactory.getInstance(ALGORITHM); + return f.generateSecret(spec).getEncoded(); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException("Missing algorithm: " + ALGORITHM, ex); + } catch (InvalidKeySpecException ex) { + throw new IllegalStateException("Invalid SecretKeyFactory", ex); + } + } + + /** + * Hash a password in an immutable {@code String}. + * + *

+ * Passwords should be stored in a {@code char[]} so that it can be filled + * with zeros after use instead of lingering on the heap and elsewhere. + * + * @param password The password to hash + * @return a secure authentication token to be stored for later + * authentication + * @deprecated Use {@link #hash(char[])} instead + */ + @Deprecated + public String hash(String password) { + return hash(password.toCharArray()); + } + +} -- GitLab From 2ca418766fb733f26611d146175d91c00d895ab8 Mon Sep 17 00:00:00 2001 From: Ramzi Benmansour Date: Fri, 5 May 2023 14:30:31 +0200 Subject: [PATCH 2/2] css for Admin user only --- src/main/java/fr/ldnr/servelets/Login.java | 19 +- src/main/webapp/WEB-INF/Admin.jsp | 17 + src/main/webapp/WEB-INF/Article.jsp | 2 +- src/main/webapp/WEB-INF/Home.jsp | 81 +---- src/main/webapp/WEB-INF/Login.jsp | 8 +- src/main/webapp/WEB-INF/Profile.jsp | 1 + src/main/webapp/WEB-INF/Signin.jsp | 13 +- src/main/webapp/WEB-INF/createArticle.jsp | 7 +- src/main/webapp/WEB-INF/jspf/Footer.jsp | 3 +- src/main/webapp/WEB-INF/jspf/Style.jsp | 361 +++++++++++++++++++++ src/main/webapp/rsc/css/Admin.css | 15 + src/main/webapp/rsc/css/Home.css | 77 +++++ src/main/webapp/rsc/css/Style.css | 1 + 13 files changed, 502 insertions(+), 103 deletions(-) create mode 100644 src/main/webapp/WEB-INF/Admin.jsp create mode 100644 src/main/webapp/WEB-INF/jspf/Style.jsp create mode 100644 src/main/webapp/rsc/css/Admin.css create mode 100644 src/main/webapp/rsc/css/Home.css diff --git a/src/main/java/fr/ldnr/servelets/Login.java b/src/main/java/fr/ldnr/servelets/Login.java index bbf2f73..445f2e8 100644 --- a/src/main/java/fr/ldnr/servelets/Login.java +++ b/src/main/java/fr/ldnr/servelets/Login.java @@ -1,6 +1,7 @@ package fr.ldnr.servelets; import Forms.LoginFormChecker; +import fr.ldnr.beans.User; import java.io.*; import java.util.*; import javax.servlet.*; @@ -11,13 +12,9 @@ import static jdk.internal.org.jline.utils.Log.error; @WebServlet(name = "login", urlPatterns = {"/login"}) public class Login extends HttpServlet { - - private static final String VIEW = "/WEB-INF/Login.jsp"; - + private static final String VIEW_ADMIN = "/WEB-INF/Admin.jsp"; - - @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -25,10 +22,13 @@ public class Login extends HttpServlet { RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher(VIEW); dispatcher.forward(request, response); } else { - response.sendRedirect(request.getContextPath() + "/home"); - + User myUser = (User) request.getSession().getAttribute("user"); + if (myUser.getId_user() == 1) { + request.getServletContext().getRequestDispatcher(VIEW_ADMIN).forward(request, response); + } else { + response.sendRedirect(request.getContextPath() + "/home"); + } } - } @Override @@ -41,10 +41,13 @@ public class Login extends HttpServlet { request.setAttribute("errors", checker.getErrors()); request.setAttribute("userData", checker.getBean()); + request.getServletContext() .getRequestDispatcher(VIEW) .forward(request, response); + } else { + //je met l'utilisateur en session request.getSession().setAttribute("user", checker.getBean()); diff --git a/src/main/webapp/WEB-INF/Admin.jsp b/src/main/webapp/WEB-INF/Admin.jsp new file mode 100644 index 0000000..a9def0c --- /dev/null +++ b/src/main/webapp/WEB-INF/Admin.jsp @@ -0,0 +1,17 @@ +<%-- + Document : Admin + Created on : 5 mai 2023, 13:03:36 + Author : stag +--%> + +<%@page contentType="text/html" pageEncoding="UTF-8"%> + + + + + Admin Page + + +

Hello World!

+ + diff --git a/src/main/webapp/WEB-INF/Article.jsp b/src/main/webapp/WEB-INF/Article.jsp index 5997bc6..51e302d 100644 --- a/src/main/webapp/WEB-INF/Article.jsp +++ b/src/main/webapp/WEB-INF/Article.jsp @@ -10,7 +10,7 @@ Article Page - "/> + <%@include file="/WEB-INF/jspf/Style.jsp" %> <%@include file="/WEB-INF/jspf/Header.jsp" %> diff --git a/src/main/webapp/WEB-INF/Home.jsp b/src/main/webapp/WEB-INF/Home.jsp index 94cac64..0f0c68e 100644 --- a/src/main/webapp/WEB-INF/Home.jsp +++ b/src/main/webapp/WEB-INF/Home.jsp @@ -3,87 +3,8 @@ <%@page contentType="text/html" pageEncoding="UTF-8"%> - "/> + <%@include file="/WEB-INF/jspf/Style.jsp" %> Blog - diff --git a/src/main/webapp/WEB-INF/Login.jsp b/src/main/webapp/WEB-INF/Login.jsp index 0300d8f..e94ac3d 100644 --- a/src/main/webapp/WEB-INF/Login.jsp +++ b/src/main/webapp/WEB-INF/Login.jsp @@ -4,7 +4,8 @@ - "/> + <%@include file="/WEB-INF/jspf/Style.jsp" %> + Connexion @@ -15,18 +16,19 @@

${errorMessage}

-
+ " required>
${errors.pseudo}
- + ${errors.pwd}
+ <%@include file="./jspf/Footer.jsp" %> diff --git a/src/main/webapp/WEB-INF/Profile.jsp b/src/main/webapp/WEB-INF/Profile.jsp index 3abb2b1..8652acf 100644 --- a/src/main/webapp/WEB-INF/Profile.jsp +++ b/src/main/webapp/WEB-INF/Profile.jsp @@ -10,6 +10,7 @@ JSP Page + <%@include file="/WEB-INF/jspf/Style.jsp" %> diff --git a/src/main/webapp/rsc/css/Admin.css b/src/main/webapp/rsc/css/Admin.css new file mode 100644 index 0000000..8c9ea39 --- /dev/null +++ b/src/main/webapp/rsc/css/Admin.css @@ -0,0 +1,15 @@ +/* +To change this license header, choose License Headers in Project Properties. +To change this template file, choose Tools | Templates +and open the template in the editor. +*/ +/* + Created on : 5 mai 2023, 13:04:01 + Author : stag +*/ + +html{ + border-color: red; + border-style: solid; + border-width: 3px; +} \ No newline at end of file diff --git a/src/main/webapp/rsc/css/Home.css b/src/main/webapp/rsc/css/Home.css new file mode 100644 index 0000000..c2f01ea --- /dev/null +++ b/src/main/webapp/rsc/css/Home.css @@ -0,0 +1,77 @@ +.article { + background-color: #f7f7f7; + border: 1px solid #ccc; + border-radius: 5px; + padding: 10px; + margin-bottom: 20px; +} + +.article h3 { + font-size: 24px; + margin-top: 0; +} + +.article .sub { + font-size: 14px; + margin-bottom: 10px; +} + +.article p { + font-size: 16px; + line-height: 1.5; + margin-bottom: 20px; +} + +.article .readMore { + text-align: right; + margin-top: 0; +} + +.article .readMore a { + color: #1a1aff; + text-decoration: none; + font-size: 18px; +} + +.article .readMore a:hover { + text-decoration: underline; +} + +.pagination { + display: flex; + justify-content: center; + margin-top: 30px; +} + +.pagination ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; +} + +.pagination li { + margin: 0 5px; +} + +.pagination li a { + display: block; + padding: 5px 10px; + color: #fff; + background-color: #1a1aff; + text-decoration: none; + border-radius: 5px; +} + +.pagination li a:hover { + background-color: #0000ff; +} + +.pagination li.active a { + background-color: #0000ff; +} + +.pagination li.disabled a { + background-color: #ccc; + cursor: not-allowed; +} \ No newline at end of file diff --git a/src/main/webapp/rsc/css/Style.css b/src/main/webapp/rsc/css/Style.css index 3d9fffa..5ae1791 100644 --- a/src/main/webapp/rsc/css/Style.css +++ b/src/main/webapp/rsc/css/Style.css @@ -10,6 +10,7 @@ form { background: #2B3A42; box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.5); } + .error { color: red; font-size: 1.8rem; -- GitLab