web-dev-qa-db-fra.com

Implémentation de la fonctionnalité de mot de passe oublié en Java

J'implémente actuellement une fonction de mot de passe oublié dans un projet Java. ma méthodologie est,

  1. L'utilisateur clique sur le lien du mot de passe oublié.
  2. Dans la page Mot de passe oublié, le système invite l'utilisateur à entrer l'adresse de messagerie dans laquelle il s'est inscrit.
  3. Un courrier électronique contenant un lien pour réinitialiser le mot de passe est envoyé à l'adresse électronique indiquée à l'étape ci-dessus.
  4. L'utilisateur clique sur le lien et il/elle est redirigé vers une page (réinitialiser le mot de passe) où l'utilisateur peut entrer son nouveau mot de passe.
  5. Sur la page Réinitialiser le mot de passe, le champ "adresse e-mail" est automatiquement renseigné et ne peut pas être modifié.
  6. Ensuite, l'utilisateur entre son nouveau mot de passe et le champ relatif à l'adresse e-mail dans la base de données est mis à jour.

Bien que j'aie restreint le champ email address de la page de réinitialisation du mot de passe (un champ en lecture seule), tout le monde peut modifier l'URL dans la barre d'adresse du navigateur et changer le champ d'adresse de messagerie.

Comment puis-je empêcher chaque utilisateur de modifier l'adresse électronique dans la page de réinitialisation du mot de passe?

16
vigamage

Vous devez l'enregistrer dans la base de données avant d'envoyer un courrier électronique à l'aide d'un jeton:

  1. Lorsque l'utilisateur clique sur "envoyer un email avec les instructions de réinitialisation", vous créez un enregistrement dans la base de données avec les champs suivants: email, token, expirationdate
  2. L'utilisateur reçoit un e-mail avec votre sitebone.fr/token et cliquez dessus
  3. Avec token dans l'URL, le serveur peut identify the user, vérifier si la demande n'est pas expirée grâce à expirationdate, mettre le bon email dans la boîte et demander le renouvellement du mot de passe. Entrez un nouveau mot de passe et vous devez donner le jeton (hidden field dans le formulaire) + mots de passe au serveur. Le serveur ne se soucie pas de la zone de texte de l'e-mail car with the token, user is identified strongly
  4. Ensuite, le serveur vérifie si le jeton est toujours valide avec expirationdate (à nouveau), vérifiez si password match et si tout va bien, sauvegardez le nouveau mot de passe! Le serveur peut envoyer un nouveau message afin d'informer l'utilisateur que le mot de passe a été modifié en raison de la demande.

C'est vraiment sans danger. Veuillez utiliser peu de temps pour que la variable expirationdate améliore la sécurité (par exemple, 5 minutes sont correctes pour moi) et utilisez un jeton fort (en tant que GUID, voir les commentaires).

35
clement

Je suis d’accord avec la réponse donnée par @clement si vous devez implémenter vous-même la fonctionnalité de mot de passe oublié. Cela ressemble à un moyen raisonnable et sécurisé pour cette implémentation.

Cependant, au lieu de cela, si vous n'avez pas à l'implémenter vous-même, je vous suggère d'utiliser un service qui le fait pour vous, comme Stormpath .

Si vous décidez d'utiliser Stormpath, le code qui déclenchera la fonctionnalité ressemblera à ceci en Java (avec le SDK Java de Stormpath):

Account account = application.sendPasswordResetEmail("[email protected]");

Votre utilisateur recevra un email avec un lien comme:

http://yoursite.com/path/to/reset/page?sptoken=$TOKEN

Et puis, lorsque l'utilisateur clique sur le lien, vous devez vérifier et réinitialiser le mot de passe comme suit:

Account account = application.resetPassword("$TOKEN", "newPassword");

Vous trouverez des détails sur son fonctionnement dans la documentation sur la réinitialisation du mot de passe password de Stormpath .

Avec cette approche, vous n'avez pas à implémenter ni à maintenir la fonctionnalité si vous avez la possibilité de ne pas le faire.

Remarque: Stormpath a rejoint Okta .

5
ecrisostomo

Vous ne pouvez pas limiter l'adresse e-mail à être modifié par l'utilisateur.
L’adresse e-mail peut facilement changer en modifiant le code source dans le navigateur, même si vous avez masqué ou créé une zone de texte en lecture seule. 

Vous pouvez fournir uniq random string or token avec le lien de réinitialisation et Vérifier l'adresse de messagerie et la combinaison de jetons après avoir cliqué sur le lien de réinitialisation de mot de passe ou après que l'utilisateur ait demandé à réinitialiser le mot de passe en vérifiant l'adresse e-mail et la chaîne de jetons dans la demande, ainsi que l'adresse votre base de données. 

Si une adresse électronique est présente dans votre base de données, cela signifie que l'adresse électronique est valide, sinon indiquez-lui que l'adresse électronique n'existe pas dans vos enregistrements d'utilisateur.

REMARQUE :
Si vous utilisez un framework ou simplement un servlet, il est préférable de fournir un lien afin que vous puissiez valider la chaîne de courrier électronique et de jeton avant d'afficher votre formulaire de réinitialisation du mot de passe. Si la chaîne de jeton ou l'adresse électronique est non valide, vous pouvez empêcher l'utilisateur de soumettre une demande de réinitialisation du mot de passe et valider après l'envoi de la demande. Il sera plus sûr que de valider après avoir soumis la demande de réinitialisation du mot de passe.

3
Yagnesh Agola

Cette question a été postée trois ans avant cette réponse ... Pourtant, je pense que cela pourrait être utile aux autres.

Donc, en bref: je suis totalement d'accord avec votre flux. Cela semble très sécurisé et votre seule extrémité ouverte est également logique - comment vous assurer que personne ne modifie le nom d'utilisateur et qui peut ainsi définir un nouveau mot de passe pour lui.

J'aime moins l'idée de stocker temporairement des choses, c'est la DB (comme le suggère la réponse acceptée).

L'idée à laquelle je pensais était de signer les données dans le lien qui est envoyé à l'utilisateur. Ensuite, lorsque l'utilisateur clique sur le lien et que le serveur reçoit l'appel, le serveur obtient également la partie chiffrée et peut valider que les données n'ont pas été modifiées.

A propos (voici une "promotion"): J'ai implémenté un projet Java pour ces cas d'utilisation (aussi "créer un compte", "changer le mot de passe" etc.). C'est gratuit sur GitHub, open source. Il répond parfaitement à votre question ... implémenté en Java, en plus de Spring Security.

Il y a une explication pour tout (et s'il manque quelque chose, faites le moi savoir ...)

Regardez: https://github.com/OhadR/oAuth2-sample/tree/master/authentication-flows

Voir une démonstration ici .

Il existe également une application Web cliente qui utilise les flux auth, avec le README avec toutes les explications: https://github.com/OhadR/Authentication-Flows

3
OhadR

Il y a deux solutions communes:

1. Creating a new password on the server and inform user from it.
2. Sending a unique URL to reset password.

La première solution a beaucoup de problèmes et n'est pas appropriée à utiliser. Voici quelques raisons:

1. The new password which is created by server should be sent through an insecure channel (such as email, sms, ...) and resides in your inbox. 

2. If somebody know the email address or phone number of a user who has an account at a website then then it is possible to reset user password.

Donc, la deuxième solution est préférable d'utiliser. Cependant, vous devriez considérer les problèmes suivants:

- The reset url should be random, not something guessable and unique to this specific instance of the reset process.

- It should not consist of any external information to the user For example, a reset URL should not simply be a path such as “.../?username=Michael”. 

- We need to ensure that the URL is loaded over HTTPS. No, posting to HTTPS is not enough, that URL with the token must implement transport layer 
  security so that the new password form cannot be MITM’d and the password the user creates is sent back over a secure connection.

- The other thing we want to do with a reset URL is setting token's expiration time so that the reset process must be completed within a certain duration.

- The reset process must run once completely. So, Reset URL can not be appilicable if the reset process is done completely once.

La solution courante peut générer une URL pour créer un jeton unique pouvant être envoyé en tant que paramètre d'URL. Elle contient une URL telle que «Reset /? Id = 2ae755640s15cd3si8c8i6s2cib9e14a1ae552b».

2
MMKarami

Si vous recherchez le code complet pour implémenter un mot de passe oublié, je partage ici mon code . Placez le lien là où vous en avez besoin.

<button> <a href="forgotpassword.jsp" style="text-decoration:none;">Forgot 
Password</a></button>

Ci-dessous ma page forgotpassword.jsp.

 <form id="register-form" role="form" class="form" method="post" 
 action="mymail_fp.jsp">
    <h3>Enter Your Email Below</h3>
   <input id="email" name="email" placeholder="Email address" class="form- 
   control"  type="email" required autofocus>
  <input name="recover-submit" class="btn btn-lg btn-primary btn-block" 
   value="Get Password" type="submit">
</form>

Une fois que l'e-mail est envoyé, il est redirigé vers la page mymail_fp.jsp où je l'envoie à l'utilisateur . La page ci-dessous est mymail.jsp.

<% 
mdjavahash md = new mdjavahash();
String smail =request.getParameter("email");
int profile_id = 0;
if(smail!=null)
{
 try{
// Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");

// Open a connection
Connection conn = 
DriverManager.getConnection("jdbc:mysql://localhost:3306/infoshare", "root", 
"");

Statement stmt = conn.createStatement();

 String sql1;
 sql1="SELECT  email FROM profile WHERE email = '"+smail+"'";

  ResultSet rs1=stmt.executeQuery(sql1);

if(rs1.first())
{
    String sql;
    sql = "SELECT Profile_id FROM profile where email='"+smail+"'";
     ResultSet rs2 = stmt.executeQuery(sql);

    // Extract data from result set
    while(rs2.next()){
       //Retrieve by column name
     profile_id  = rs2.getInt("Profile_id");
    }

    Java.sql.Timestamp  intime = new Java.sql.Timestamp(new 
    Java.util.Date().getTime());
    Calendar cal = Calendar.getInstance();
    cal.setTimeInMillis(intime.getTime());
    cal.add(Calendar.MINUTE, 20);
    Java.sql.Timestamp  exptime = new Timestamp(cal.getTime().getTime());

    int Rand_num = (int) (Math.random() * 1000000);
    String Rand = Integer.toString(Rand_num);
    String finale =(Rand+""+intime); // 
    String hash = md.getHashPass(finale); //hash code

    String save_hash = "insert into  reset_password (Profile_id, hash_code, 
   exptime, datetime) values("+profile_id+", '"+hash+"', '"+exptime+"', 
   '"+intime+"')";
    int saved = stmt.executeUpdate(save_hash);
    if(saved>0)
    {
  String link = "http://localhost:8080/Infoshare/reset_password.jsp";     
  //bhagawat till here, you have fetch email and verified with the email 
 from 
  datbase and retrived password from the db.
    //-----------------------------------------------
String Host="", user="", pass=""; 
Host = "smtp.gmail.com"; user = "[email protected]"; 
//"email@removed" // email id to send the emails 
pass = "xxxx"; //Your gmail password 
String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"; 
String to = smail;  
String from = "[email protected]";  
String subject = "Password Reset"; 
 String messageText = " Click <a href="+link+"?key="+hash+">Here</a> To 
  Reset 
  your Password. You must reset your password within 20 
  minutes.";//messageString; 
   String fileAttachment = ""; 
   boolean WasEmailSent ; 
  boolean sessionDebug = true; 
  Properties props = System.getProperties(); 
  props.put("mail.Host", Host); 
  props.put("mail.transport.protocol.", "smtp"); 
  props.put("mail.smtp.auth", "true"); 
  props.put("mail.smtp.", "true"); 
  props.put("mail.smtp.port", "465"); 
  props.put("mail.smtp.socketFactory.fallback", "false"); 
  props.put("mail.smtp.socketFactory.class", SSL_FACTORY); 
  Session mailSession = Session.getDefaultInstance(props, null); 
  mailSession.setDebug(sessionDebug); 
  Message msg = new MimeMessage(mailSession); 
  msg.setFrom(new InternetAddress(from)); 
  InternetAddress[] address = {new InternetAddress(to)}; 
  msg.setRecipients(Message.RecipientType.TO, address); 
  msg.setSubject(subject); 
  msg.setContent(messageText, "text/html");  
  Transport transport = mailSession.getTransport("smtp"); 
  transport.connect(Host, user, pass);
    %>
 <div class="alert success" style="padding: 30px; background-color: grey; 
  color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
 5% 
15% 20%;">
 <a href="forgotpassword.jsp"> <span class="closebtn" style="color: white; 
font-weight: bold; float: right; font-size: 40px; line-height: 35px; cursor: 
pointer; transition: 0.3s;">&times;</span> </a> 
 <h1 style="font-size:30px;">&nbsp;&nbsp; <strong>Check Your Email. Link To 
Reset Your Password Is Sent To : <%out.println(" "+smail); %></strong>  
</h1>
 <center><a href="forgotpassword.jsp"><h2><input type="button" value="OK"> 
</h2></a></center>
</div>
<%
try { 
transport.sendMessage(msg, msg.getAllRecipients()); 
WasEmailSent = true; // assume it was sent 
} 
catch (Exception err) { 
WasEmailSent = false; // assume it's a fail 
} 
 transport.close();
    //-----------------------------------------------
 }  
}   

 else{
    %>
    <div class="alert success" style="padding: 30px; background-color: grey; 
 color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
 5% 15% 20%;">
     <a href="forgotpassword.jsp"> <span class="closebtn" style="color: 
 white; font-weight: bold; float: right; font-size: 40px; line-height: 35px; 
 cursor: pointer; transition: 0.3s;">&times;</span> </a> 
     <h1 style="font-size:30px;">&nbsp;&nbsp; <strong>There Is No Email As 
 Such <%out.println(" "+smail); %></strong>Try Again  </h1>
     <center><a href="forgotpassword.jsp"><h2><input type="button" 
 value="OK"></h2></a></center>
    </div>
    <%      
 }  

stmt.close();
rs1.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}
}
 else{
    %>
 <div class="alert success" style="padding: 30px; background-color: grey; 
 color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
 5% 15% 20%;">
  <a href="forgotpassword.jsp"> <span class="closebtn" style="color: white; 
  font-weight: bold; float: right; font-size: 40px; line-height: 35px; 
 cursor: 
 pointer; transition: 0.3s;">&times;</span> </a> 
 <h1 style="font-size:30px;">&nbsp;&nbsp; <strong>Please Enter The Valid 
 Email Address</strong>  </h1>
 <center><a href="forgotpassword.jsp"><h2><input type="button" value="OK"> 
 </h2></a></center>
 </div>
  <%    
  }
  %> 

Maintenant, ce que j'ai fait ici, c’est, avant d’envoyer un courrier électronique à l’utilisateur, j’économise du temps d’envoi, un délai d’expiration, un nombre aléatoire de 0 à 1 000, puis concatène avec le temps d’envoi et le cryptage est envoyé et envoyé en chaîne de requête le courriel. Donc, l'email sera envoyé et le lien vers le mot de passe sera envoyé avec la clé de hachage. Désormais, lorsque l'utilisateur clique sur le lien, il est envoyé à reset_password.jsp. La page suivante est reset_password.jsp.

<%
String hash = (request.getParameter("key"));

Java.sql.Timestamp  curtime = new Java.sql.Timestamp(new 
Java.util.Date().getTime());

int profile_id = 0;
Java.sql.Timestamp exptime;

try{
// Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");

// Open a connection
Connection conn = 
DriverManager.getConnection("jdbc:mysql://localhost:3306/infoshare", "root", 
"");
Statement stmt = conn.createStatement();

 String sql = "select profile_id, exptime from reset_password where 
 hash_code ='"+hash+"'";
 ResultSet rs = stmt.executeQuery(sql);
 if(rs.first()){
 profile_id = rs.getInt("Profile_id");  
 exptime = rs.getTimestamp("exptime");

  //out.println(exptime+"/"+curtime);
  if((curtime).before(exptime)){        
      %>
      <div class="container">
       <form class="form-signin" action="update_reset.jsp" method="Post"> 
      <br/><br/>
         <h4 class="form-signin-heading">Reset Your Password Here</h4>
         <br> 
          <text style="font-size:13px;"><span class="req" 
        style="color:red">* </span>Enter New Password</text>
         <input type="password" id="inputPassword" name="newpassword" 
       class="form-control" placeholder="New Password" required autofocus>
         <br>
          <text style="font-size:13px;"><span class="req" 
         style="color:red">* </span>Enter New Password Again</text>
         <input type="password" id="inputPassword" name="confirmpassword" 
         class="form-control" placeholder="New Password Again" required>

          <input type="hidden" name="profile_id" value=<%=profile_id %>>
        <br>
         <button class="btn btn-lg btn-primary btn-block" 
    type="submit">Reset Password</button>
       </form>
     </div> <!-- /container -->
    <% } 
    else{
        %>
        <div class="alert success" style="padding: 30px; background-color: 
   grey; color: white; opacity: 1; transition: opacity 0.6s; width:50%; 
  margin: 10% 5% 15% 20%;">
             <a href="forgotpassword.jsp"> <span class="closebtn" 
   style="color: white; font-weight: bold; float: right; font-size: 40px; 
   line-height: 35px; cursor: pointer; transition: 0.3s;">&times;</span> 
   </a> 
             <h1 style="font-size:30px;">&nbsp;&nbsp; The Time To Reset 
  Password Has Expired.<br> &nbsp;&nbsp; Try Again </h1>
             <center><a href="forgotpassword.jsp"><h2><input type="button" 
     value="OK"></h2></a></center>
        </div>
       <%       
       }    
     }
   else{
    %>
    <div class="alert success" style="padding: 30px; background-color: grey; 
   color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 
    10% 5% 15% 20%;">
         <a href="forgotpassword.jsp"> <span class="closebtn" style="color: 
      white; font-weight: bold; float: right; font-size: 40px; line-height: 
       35px; cursor: pointer; transition: 0.3s;">&times;</span> </a> 
         <h1 style="font-size:30px;">&nbsp;&nbsp; The Hash Key DO Not Match. 
            <br/> &nbsp;&nbsp;&nbsp;Try Again!! </h1>
         <center><a href="forgotpassword.jsp"><h2><input type="button" 
         value="OK"></h2></a></center>
        </div>
    <%
    }
   // Clean-up environment
   rs.close();
   stmt.close();
   conn.close();
  }catch(SQLException se){
  //Handle errors for JDBC
  se.printStackTrace();
 }catch(Exception e){
  e.printStackTrace();
  }
%> 

Dans cette page, j'extraye la clé de hachage et la compare à la clé de hachage de la base de données. C'est vrai, puis j'extraye le délai d'expiration et le compare à l'heure actuelle. Si le temps nécessaire pour réinitialiser le mot de passe n'a pas expiré, alors je montre le formulaire pour réinitialiser le mot de passe, sinon je jette un message d'erreur. Si le temps n'a pas expiré, je montre le formulaire et lorsque le formulaire est soumis, il est redirigé vers update_reset.jsp et la page suivante est ma page update_reset.jsp.

 <%  
 mdjavahash md = new mdjavahash();
 String profile_id= request.getParameter("profile_id");
 String np= request.getParameter("newpassword");
 String cp = request.getParameter("confirmpassword");
 //out.println(np +"/"+ cp);

 if( np.equals(" ") || cp.equals(" ")){%>
 <div class="alert success" style="padding: 30px; background-color: grey; 
 color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
 5% 15% 20%;">
     <a href="reset_password?profile_id=<%=profile_id%>"> <span 
  class="closebtn" style="color: white; font-weight: bold; float: right; 
    font-size: 40px; line-height: 35px; cursor: pointer; transition: 
   0.3s;">&times;</span> </a> 
     <h1 style="font-size:30px;">&nbsp;&nbsp; Please Fill Both The Fields 
    </h1>
     <center><a href="reset_password?profile_id=<%=profile_id%>""><h2><input 
    type="button" value="OK"></h2></a></center>
   </div>   
   <% }
   else if(!np.equals(cp)){
    %>
    <div class="alert success" style="padding: 30px; background-color: grey; 
  color: white; opacity: 1; transition: opacity 0.6s; width:50%; margin: 10% 
  5% 15% 20%;">
         <a href="reset_password?profile_id=<%=profile_id%>"> <span 
     class="closebtn" style="color: white; font-weight: bold; float: right; 
        font-size: 40px; line-height: 35px; cursor: pointer; transition: 
             0.3s;">&times;</span> </a> 
         <h1 style="font-size:30px;">&nbsp;&nbsp; The Two Passwords Do Not 
        Match. Try Again </h1>
         <center><a href="reset_password?profile_id=<%=profile_id%>"><h2> 
           <input type="button" value="OK"></h2></a></center>
        </div>
      <%        
     }
    else{   
      try{
        // Register JDBC driver
        Class.forName("com.mysql.jdbc.Driver");

        // Open a connection
        Connection conn = 
        DriverManager.getConnection("jdbc:mysql://localhost:3306/infoshare", 
      "root", "");
        // Execute SQL query
        Statement stmt = conn.createStatement();
        stmt.executeUpdate("update profile set 
       password='"+md.getHashPass(np)+"' where Profile_id="+profile_id+"");
        //response.sendRedirect("mainpage.jsp");
        %>
        <div class="alert success" style="padding: 30px; background-color: 
       grey; color: white; opacity: 1; transition: opacity 0.6s; width:65%; 
      margin: 10% 5% 15% 20%;">
         <a href="login.jsp"> <span class="closebtn" style="color: white; 
        font-weight: bold; float: right; font-size: 40px; line-height: 35px; 
         cursor: pointer; transition: 0.3s;">&times;</span> </a> 
         <h1 style="font-size:30px;">&nbsp;&nbsp; The Password Is 
            Successfully Reset.<br>&nbsp;&nbsp; Try Login With New 
             Password</h1>
         <br><br><center><a href="login.jsp"><p style="font-size:20px"> 
            <input type="button" style="width:40px; height:35px;" 
        value="OK"></p></a> 
        </center>
           </div>                   
          <%
           stmt.close();
           conn.close();
        }catch(SQLException se){
          //Handle errors for JDBC
           se.printStackTrace();
        }catch(Exception e){
        //Handle errors for Class.forName
         e.printStackTrace();
       }    
  } 
%>

Dans cette page, je valide d'abord les champs, puis je mets à jour la base de données avec le nouveau mot de passe. Bien que ce soit long mais que ça marche. J'ai utilisé la technique de cryptage MD5 ici et si vous voulez savoir comment faire, suivez le lien Comment utiliser MD5 Hash pour sécuriser les mots de passe de connexion dans JSP avec Javascript?

1
Bhagawat