在Java中实现忘记密码功能

问题描述 投票:16回答:6

我目前正在Java项目中实现忘记密码功能。 我的方法是,

  1. 用户单击忘记密码链接。
  2. 在忘记密码页面中,系统提示用户输入他/她已注册到系统的电子邮件地址。
  3. 包含重置密码的链接的电子邮件将在上面的步骤中发送到给定的电子邮件地址。
  4. 用户单击该链接,他/她将被重定向到页面(重置密码),用户可以在其中输入新密码。
  5. 在“重置密码”页面中,“电子邮件地址”字段自动填写,无法更改。
  6. 然后用户输入他的新密码,并更新与数据库中的电子邮件地址相关的字段。

虽然我已将限制重置密码页面中的email address字段限制为编辑(只读字段),但任何人都可以更改浏览器地址栏中的URL并更改电子邮件地址字段。

如何限制每个用户更改重置密码页面中的电子邮件地址?

java password-recovery forgot-password change-password
6个回答
36
投票

在使用令牌发送电子邮件之前,您必须将其保存在数据库中:

  1. 当用户单击“向我发送包含重置指令的电子邮件”时,您将在DB中创建一条记录,其中包含以下字段: emailtokenexpirationdate
  2. 用户使用yourwwebsite.com/token接收电子邮件并单击它
  3. 使用Url中的token ,服务器可以identify the user ,检查请求是否因expirationdate而过期,将正确的电子邮件放入框中,并要求更新密码。 用户键入新密码,您必须将令牌(表单中的hidden field )+密码提供给服务器。 服务器不关心电子邮件的文本框,因为with the token, user is identified strongly
  4. 然后服务器检查令牌是否仍然有效,使用expirationdate (再次),检查password match ,如果一切正常,请保存新密码! 服务器可以再次发送消息,以通知用户由于请求已更改密码。

这真的很安全。 请使用expirationdate短时间来改善安全性(例如5分钟对我来说是正确的)并使用强令牌(作为GUID,请参阅注释)


5
投票

如果您必须自己实现忘记密码功能,我同意@clement给出的答案。 听起来像这种实现的合理和安全的方式。

但是,作为替代方案,如果您不必自己实现它,我建议您使用为您执行此操作的服务,例如Stormpath

如果您决定使用Stormpath,那么触发该功能的代码在Java中就会如此(使用Stormpath的Java SDK):

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

您的用户会收到一封包含以下链接的电子邮件:

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

然后,当用户点击链接时,您将验证并重置密码,如下所示:

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

有关其工作原理的详细信息,请参阅Stormpath的密码重置文档

使用此方法,如果您可以选择不这样做,则不必为自己实现和维护功能。

注意:Stormpath加入了Okta


3
投票

您无法限制电子邮件地址被用户更改。
即使您已将隐藏或制作文本框作为只读文件框,也可以通过在浏览器中编辑源代码轻松更改电子邮件地址。

您可以通过重置链接提供uniq random string or token ,并在点击重置密码链接后或在用户提交重置密码请求后检查电子邮件地址和令牌组合,方法是使用电子邮件地址和令牌字符串检查请求中的电子邮件地址和令牌字符串在您的数据库中。

如果您的数据库中存在电子邮件地址,这意味着电子邮件地址有效,如果不是,则表示您的用户记录中不存在电子邮件地址。

注意 :
如果您使用任何框架或仅使用servlet而不是提供链接,那么您可以在显示重置密码表单之前验证电子邮件和令牌字符串。 如果令牌字符串或电子邮件地址无效,则可以限制用户提交重置密码请求并在提交请求后进行验证。 提交重置密码请求后,它比验证更安全。


3
投票

这个问题已在此答案发布前3年发布......但我认为这可能对其他人有所帮助。

简而言之:我完全赞同你的流程。 看起来很安全,你唯一的开放端也很有意义 - 你怎么能确保没有人改变用户名,并且可以为他设置一个新密码。

我更喜欢暂时存储东西的想法是数据库(正如接受的答案所暗示的那样)。

我正在考虑的想法是在发送给用户的链接中签署数据。 然后,当用户单击链接并且服务器接收到呼叫时,服务器也会获取加密部分并可以验证数据是否未被触及。

顺便说一句(这里是“促销”):我已经为这些用例实现了一个JAVA项目(也包括“创建帐户”,“更改密码”等)。 它是免费的GitHub,开源。 它完美地回答了你的问题......在Spring安全的基础上用Java实现。

一切都有解释(如果缺少某些东西 - 请告诉我......)

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

在这里看一个演示

还有一个使用auth-flow的客户端Web应用程序,README包含所有解释: https//github.com/OhadR/Authentication-Flows


2
投票

有两种常见的解决方案:

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

第一种解决方案存在很多问题,不适合使用。 这些是一些原因:

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.

所以,第二种解决方案更好用。 但是,您应该考虑以下问题:

- 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.

常见的解决方案是生成URL以创建可以作为URL参数发送的唯一令牌,它包含诸如“Reset /?id = 2ae755640s15cd3si8c8i6s2cib9e14a1ae552b”之类的URL。


1
投票

如果您正在寻找实现忘记密码的完整代码,请在这里分享我的代码。 将链接放在您需要的位置。

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

下面是我的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>

提交电子邮件后,它会被重定向到mymail_fp.jsp页面,我将电子邮件发送给用户。 下面是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>
  <%    
  }
  %> 

现在我在这里做的是,在向用户发送电子邮件发送电子邮件之前,我保存发送时间,到期时间,生成从0到1000000的随机数,并与发送时间连接并加密,并将其作为查询字符串发送到链接中电子邮件。 因此,将发送电子邮件并将密码链接与密码一起发送。 现在,当用户点击链接时,会将它们发送到reset_password.jsp,然后是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();
  }
%> 

在这个页面中,我获取散列键并与数据库散列键进行比较,它是真的,然后我获取过期时间并与当前时间进行比较。 如果重置密码的时间没有到期,那么我显示表单重置密码,否则我抛出错误信息。 如果时间尚未到期,那么我会显示表单,当表单提交时,它会被重定向到update_reset.jsp ,以下是我的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();
       }    
  } 
%>

在此页面中,我首先验证字段,然后使用新密码更新数据库。 虽然很长但是有效。 我在这里使用了MD5加密技术,如果你想要怎么做,请点击链接如何使用MD5 Hash在JSP中使用Javascript保护登录密码?

© www.soinside.com 2019 - 2024. All rights reserved.